diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-18 05:52:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-18 05:52:22 +0000 |
commit | 38b7c80217c4e72b1d8988eb1e60bb6e77334114 (patch) | |
tree | 356e9fd3762877d07cde52d21e77070aeff7e789 /ansible_collections/community/general/tests | |
parent | Adding upstream version 7.7.0+dfsg. (diff) | |
download | ansible-38b7c80217c4e72b1d8988eb1e60bb6e77334114.tar.xz ansible-38b7c80217c4e72b1d8988eb1e60bb6e77334114.zip |
Adding upstream version 9.4.0+dfsg.upstream/9.4.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/community/general/tests')
428 files changed, 20413 insertions, 2951 deletions
diff --git a/ansible_collections/community/general/tests/.gitignore b/ansible_collections/community/general/tests/.gitignore index 6edf5dc10..0d36555dd 100644 --- a/ansible_collections/community/general/tests/.gitignore +++ b/ansible_collections/community/general/tests/.gitignore @@ -3,3 +3,4 @@ # SPDX-License-Identifier: GPL-3.0-or-later output/ +integration/inventory diff --git a/ansible_collections/community/general/tests/galaxy-importer.cfg b/ansible_collections/community/general/tests/galaxy-importer.cfg new file mode 100644 index 000000000..5ab20d06a --- /dev/null +++ b/ansible_collections/community/general/tests/galaxy-importer.cfg @@ -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 + +[galaxy-importer] +# This is only needed to make Zuul's third-party-check happy. +# It is not needed by anything else. +run_ansible_doc=false diff --git a/ansible_collections/community/general/tests/integration/targets/aix_filesystem/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/aix_filesystem/tasks/main.yml index 25146062d..878088f4e 100644 --- a/ansible_collections/community/general/tests/integration/targets/aix_filesystem/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/aix_filesystem/tasks/main.yml @@ -90,7 +90,7 @@ size: -2G state: present -- name: Resizing /mksysb to 100G (no enought space) +- name: Resizing /mksysb to 100G (not enough space) aix_filesystem: filesystem: /mksysb size: +100G diff --git a/ansible_collections/community/general/tests/integration/targets/alternatives/tasks/tests_set_priority.yml b/ansible_collections/community/general/tests/integration/targets/alternatives/tasks/tests_set_priority.yml index 46cf48e59..9bc523b0d 100644 --- a/ansible_collections/community/general/tests/integration/targets/alternatives/tasks/tests_set_priority.yml +++ b/ansible_collections/community/general/tests/integration/targets/alternatives/tasks/tests_set_priority.yml @@ -22,7 +22,7 @@ assert: that: - 'alternative is changed' - - 'cmd.stdout == "dummy{{ item }}"' + - 'cmd.stdout == "dummy" ~ item' - name: check that alternative has been updated command: "grep -Pzq '/bin/dummy{{ item }}\\n{{ 60 + item|int }}' '{{ alternatives_dir }}/dummy'" diff --git a/ansible_collections/community/general/tests/integration/targets/ansible_galaxy_install/aliases b/ansible_collections/community/general/tests/integration/targets/ansible_galaxy_install/aliases index 13655b194..297477ac9 100644 --- a/ansible_collections/community/general/tests/integration/targets/ansible_galaxy_install/aliases +++ b/ansible_collections/community/general/tests/integration/targets/ansible_galaxy_install/aliases @@ -4,5 +4,4 @@ azp/posix/3 destructive -skip/python2.6 context/controller # While this is not really true, this module mainly is run on the controller, *and* needs access to the ansible-galaxy CLI tool diff --git a/ansible_collections/community/general/tests/integration/targets/apache2_module/tasks/actualtest.yml b/ansible_collections/community/general/tests/integration/targets/apache2_module/tasks/actualtest.yml index 3301a16b1..6fd10ce57 100644 --- a/ansible_collections/community/general/tests/integration/targets/apache2_module/tasks/actualtest.yml +++ b/ansible_collections/community/general/tests/integration/targets/apache2_module/tasks/actualtest.yml @@ -73,7 +73,7 @@ state: absent force: true - - name: reenable autoindex + - name: re-enable autoindex community.general.apache2_module: name: autoindex state: present diff --git a/ansible_collections/community/general/tests/integration/targets/apk/aliases b/ansible_collections/community/general/tests/integration/targets/apk/aliases new file mode 100644 index 000000000..6e8c01586 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/apk/aliases @@ -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 + +azp/posix/2 +needs/root +destructive +skip/aix +skip/osx +skip/macos +skip/freebsd +skip/rhel +skip/ubuntu
\ No newline at end of file diff --git a/ansible_collections/community/general/tests/integration/targets/apk/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/apk/tasks/main.yml new file mode 100644 index 000000000..0e1b0ae42 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/apk/tasks/main.yml @@ -0,0 +1,160 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Copyright (c) 2024, Max Maxopoly <max@dermax.org> +# 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: Run apk tests on Alpine + when: ansible_distribution in ['Alpine'] + block: + - name: Ensure vim is not installed + community.general.apk: + name: vim + state: absent + + - name: Install vim + community.general.apk: + name: vim + state: present + register: results + + - name: Ensure vim was installed + ansible.builtin.assert: + that: + - results is changed + - (results.packages | length) >= 1 # vim has dependencies, so depending on the base image this number may vary + + - name: Install vim again + community.general.apk: + name: vim + state: present + register: results + + - name: Ensure vim was not installed again + ansible.builtin.assert: + that: + - results is not changed + - (results.packages | default([]) | length) == 0 + + - name: Ensure vim is not installed + community.general.apk: + name: vim + state: absent + register: results + + - name: Ensure vim was uninstalled + ansible.builtin.assert: + that: + - results is changed + - (results.packages | length) >= 1 + + - name: Install vim without cache + community.general.apk: + name: vim + state: present + no_cache: true + register: results + + - name: Ensure vim was installed without cache + ansible.builtin.assert: + that: + - results is changed + + - name: Install vim again without cache + community.general.apk: + name: vim + state: present + no_cache: true + register: results + + - name: Ensure vim was not installed again without cache + ansible.builtin.assert: + that: + - results is not changed + - (results.packages | default([]) | length) == 0 + + - name: Ensure a bunch of packages aren't installed + community.general.apk: + name: + - less + - nano + - vim + state: absent + + - name: Install a bunch of packages + community.general.apk: + name: + - less + - nano + - vim + state: present + register: results + + - name: Ensure a bunch of packages were installed + ansible.builtin.assert: + that: + - results is changed + - (results.packages | length) >= 3 + + - name: Install a bunch of packages again + community.general.apk: + name: + - less + - nano + - vim + state: present + register: results + + - name: Ensure a bunch of packages were not installed again + ansible.builtin.assert: + that: + - results is not changed + - (results.packages | default([]) | length) == 0 + + - name: Ensure a bunch of packages are not installed + community.general.apk: + name: + - less + - nano + - vim + state: absent + register: results + + - name: Ensure a bunch of packages were uninstalled + ansible.builtin.assert: + that: + - results is changed + - (results.packages | length) >= 3 + + - name: Install a bunch of packages without cache + community.general.apk: + name: + - less + - nano + - vim + state: present + no_cache: true + register: results + + - name: Ensure a bunch of packages were installed without cache + ansible.builtin.assert: + that: + - results is changed + + - name: Install a bunch of packages again without cache + community.general.apk: + name: + - less + - nano + - vim + state: present + no_cache: true + register: results + + - name: Ensure a bunch of packages were not installed again without cache + ansible.builtin.assert: + that: + - results is not changed + - (results.packages | default([]) | length) == 0 diff --git a/ansible_collections/community/general/tests/integration/targets/archive/tests/core.yml b/ansible_collections/community/general/tests/integration/targets/archive/tests/core.yml index 1c4f4d1aa..21b07038f 100644 --- a/ansible_collections/community/general/tests/integration/targets/archive/tests/core.yml +++ b/ansible_collections/community/general/tests/integration/targets/archive/tests/core.yml @@ -29,7 +29,7 @@ that: - archive_no_options is changed - "archive_no_options.dest_state == 'archive'" - - "{{ archive_no_options.archived | length }} == 3" + - "archive_no_options.archived | length == 3" - name: Remove the archive - no options ({{ format }}) file: @@ -54,7 +54,7 @@ that: - archive_file_options_stat is not changed - "archive_file_options.mode == '0600'" - - "{{ archive_file_options.archived | length }} == 3" + - "archive_file_options.archived | length == 3" - name: Remove the archive - file options ({{ format }}) file: @@ -146,7 +146,7 @@ assert: that: - archive_path_list is changed - - "{{ archive_path_list.archived | length }} == 3" + - "archive_path_list.archived | length == 3" - name: Remove archive - path list ({{ format }}) file: @@ -168,8 +168,8 @@ that: - archive_missing_paths is changed - "archive_missing_paths.dest_state == 'incomplete'" - - "'{{ remote_tmp_dir }}/dne.txt' in archive_missing_paths.missing" - - "'{{ remote_tmp_dir }}/foo.txt' not in archive_missing_paths.missing" + - "(remote_tmp_dir ~ '/dne.txt') in archive_missing_paths.missing" + - "(remote_tmp_dir ~ '/foo.txt') not in archive_missing_paths.missing" - name: Remove archive - missing paths ({{ format }}) file: diff --git a/ansible_collections/community/general/tests/integration/targets/archive/tests/remove.yml b/ansible_collections/community/general/tests/integration/targets/archive/tests/remove.yml index 8f0b8cff8..a7e151d25 100644 --- a/ansible_collections/community/general/tests/integration/targets/archive/tests/remove.yml +++ b/ansible_collections/community/general/tests/integration/targets/archive/tests/remove.yml @@ -20,21 +20,28 @@ assert: that: - archive_remove_source_files is changed - - "{{ archive_remove_source_files.archived | length }} == 3" + - "archive_remove_source_files.archived | length == 3" - name: Remove Archive - remove source files ({{ format }}) file: path: "{{ remote_tmp_dir }}/archive_remove_source_files.{{ format }}" state: absent -- name: Assert that source files were removed - remove source files ({{ format }}) - assert: - that: - - "'{{ remote_tmp_dir }}/{{ item }}' is not exists" +- name: Remove source files in check mode ({{ format }}) + file: + path: "{{ remote_tmp_dir }}/{{ item }}" + state: absent + check_mode: true with_items: - foo.txt - bar.txt - empty.txt + register: remove_files + +- name: Assert that source files were removed - remove source files ({{ format }}) + assert: + that: + - remove_files is not changed - name: Copy source files - remove source directory ({{ format }}) copy: @@ -76,17 +83,24 @@ assert: that: - archive_remove_source_directory is changed - - "{{ archive_remove_source_directory.archived | length }} == 3" + - "archive_remove_source_directory.archived | length == 3" - name: Remove archive - remove source directory ({{ format }}) file: path: "{{ remote_tmp_dir }}/archive_remove_source_directory.{{ format }}" state: absent +- name: Remove source source directory in check mode ({{ format }}) + file: + path: "{{ remote_tmp_dir }}/tmpdir" + state: absent + check_mode: true + register: remove_dir + - name: Verify source directory was removed - remove source directory ({{ format }}) assert: that: - - "'{{ remote_tmp_dir }}/tmpdir' is not exists" + - remove_dir is not changed - name: Create temporary directory - remove source excluding path ({{ format }}) file: @@ -120,7 +134,7 @@ assert: that: - archive_remove_source_excluding_path is changed - - "{{ archive_remove_source_excluding_path.archived | length }} == 2" + - "archive_remove_source_excluding_path.archived | length == 2" - name: Remove archive - remove source excluding path ({{ format }}) file: diff --git a/ansible_collections/community/general/tests/integration/targets/btrfs_subvolume/aliases b/ansible_collections/community/general/tests/integration/targets/btrfs_subvolume/aliases index 914c36ad3..f5b6800db 100644 --- a/ansible_collections/community/general/tests/integration/targets/btrfs_subvolume/aliases +++ b/ansible_collections/community/general/tests/integration/targets/btrfs_subvolume/aliases @@ -1,4 +1,4 @@ -# Copyright (c) Ansible Projec +# 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 diff --git a/ansible_collections/community/general/tests/integration/targets/callback_default_without_diff/aliases b/ansible_collections/community/general/tests/integration/targets/callback_default_without_diff/aliases new file mode 100644 index 000000000..3e2dd244c --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/callback_default_without_diff/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/posix/3 +needs/target/callback diff --git a/ansible_collections/community/general/tests/integration/targets/callback_default_without_diff/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/callback_default_without_diff/tasks/main.yml new file mode 100644 index 000000000..5fc656e84 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/callback_default_without_diff/tasks/main.yml @@ -0,0 +1,65 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# 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 + +- block: + - name: Create temporary file + tempfile: + register: tempfile + + - name: Run tests + include_role: + name: callback + vars: + tests: + - name: Basic file diff + environment: + ANSIBLE_NOCOLOR: 'true' + ANSIBLE_FORCE_COLOR: 'false' + ANSIBLE_DIFF_ALWAYS: 'true' + ANSIBLE_PYTHON_INTERPRETER: "{{ ansible_python_interpreter }}" + ANSIBLE_STDOUT_CALLBACK: community.general.default_without_diff + playbook: | + - hosts: testhost + gather_facts: true + tasks: + - name: Create file + copy: + dest: "{{ tempfile.path }}" + content: | + Foo bar + + - name: Modify file + copy: + dest: "{{ tempfile.path }}" + content: | + Foo bar + Bar baz bam! + expected_output: [ + "", + "PLAY [testhost] ****************************************************************", + "", + "TASK [Gathering Facts] *********************************************************", + "ok: [testhost]", + "", + "TASK [Create file] *************************************************************", + "changed: [testhost]", + "", + "TASK [Modify file] *************************************************************", + "changed: [testhost]", + "", + "PLAY RECAP *********************************************************************", + "testhost : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ", + ] + + always: + - name: Clean up temp file + file: + path: "{{ tempfile.path }}" + state: absent diff --git a/ansible_collections/community/general/tests/integration/targets/cargo/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/cargo/tasks/main.yml index bb22e27c0..29f27c3fd 100644 --- a/ansible_collections/community/general/tests/integration/targets/cargo/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/cargo/tasks/main.yml @@ -18,3 +18,5 @@ - import_tasks: test_version.yml environment: "{{ cargo_environment }}" when: has_cargo | default(false) +- import_tasks: test_rustup_cargo.yml + when: rustup_cargo_bin | default(false) diff --git a/ansible_collections/community/general/tests/integration/targets/cargo/tasks/setup.yml b/ansible_collections/community/general/tests/integration/targets/cargo/tasks/setup.yml index 232658ab4..7eec97ac4 100644 --- a/ansible_collections/community/general/tests/integration/targets/cargo/tasks/setup.yml +++ b/ansible_collections/community/general/tests/integration/targets/cargo/tasks/setup.yml @@ -26,3 +26,17 @@ has_cargo: true when: - ansible_system == 'FreeBSD' and ansible_distribution_version is version('13.0', '>') + +- block: + - name: Download rustup + get_url: + url: https://sh.rustup.rs + dest: /tmp/sh.rustup.rs + mode: "0750" + force: true + - name: Install rustup cargo + command: /tmp/sh.rustup.rs -y + - set_fact: + rustup_cargo_bin: "{{ lookup('env', 'HOME') }}/.cargo/bin/cargo" + when: + - ansible_distribution != 'CentOS' or ansible_distribution_version is version('7.0', '>=') diff --git a/ansible_collections/community/general/tests/integration/targets/cargo/tasks/test_rustup_cargo.yml b/ansible_collections/community/general/tests/integration/targets/cargo/tasks/test_rustup_cargo.yml new file mode 100644 index 000000000..ec2cf6e6d --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/cargo/tasks/test_rustup_cargo.yml @@ -0,0 +1,23 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later +# +- name: Install application helloworld + community.general.cargo: + executable: "{{ rustup_cargo_bin }}" + name: helloworld + register: rustup_install_absent_helloworld + +- name: Uninstall application helloworld + community.general.cargo: + executable: "{{ rustup_cargo_bin }}" + state: absent + name: helloworld + register: rustup_uninstall_present_helloworld + +- name: Check assertions helloworld + assert: + that: + - rustup_install_absent_helloworld is changed + - rustup_uninstall_present_helloworld is changed diff --git a/ansible_collections/community/general/tests/integration/targets/cloud_init_data_facts/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/cloud_init_data_facts/tasks/main.yml index 40e762d68..2b67b5c17 100644 --- a/ansible_collections/community/general/tests/integration/targets/cloud_init_data_facts/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/cloud_init_data_facts/tasks/main.yml @@ -8,6 +8,14 @@ # 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: Help debugging + debug: + msg: >- + distribution={{ ansible_distribution }}, + distribution major version={{ ansible_distribution_major_version }}, + os_family={{ ansible_os_family }}, + Python version={{ ansible_python.version.major }} + - name: test cloud-init # TODO: check for a workaround # install 'cloud-init'' failed: dpkg-divert: error: `diversion of /etc/init/ureadahead.conf @@ -15,10 +23,11 @@ # /etc/init/ureadahead.conf to /etc/init/ureadahead.conf.distrib # https://bugs.launchpad.net/ubuntu/+source/ureadahead/+bug/997838 # Will also have to skip on OpenSUSE when running on Python 2 on newer Leap versions - # (!= 42 and >= 15) ascloud-init will install the Python 3 package, breaking our build on py2. + # (!= 42 and >= 15) as cloud-init will install the Python 3 package, breaking our build on py2. when: - not (ansible_distribution == "Ubuntu" and ansible_distribution_major_version|int == 14) - not (ansible_os_family == "Suse" and ansible_distribution_major_version|int != 42 and ansible_python.version.major != 3) + - not (ansible_os_family == "Suse" and ansible_distribution_major_version|int == 15) - not (ansible_distribution == "CentOS" and ansible_distribution_major_version|int == 8) # TODO: cannot start service - not (ansible_distribution == 'Archlinux') # TODO: package seems to be broken, cannot be downloaded from mirrors? - not (ansible_distribution == 'Alpine') # TODO: not sure what's wrong here, the module doesn't return what the tests expect diff --git a/ansible_collections/community/general/tests/integration/targets/cmd_runner/action_plugins/_unsafe_assert.py b/ansible_collections/community/general/tests/integration/targets/cmd_runner/action_plugins/_unsafe_assert.py new file mode 100644 index 000000000..498e8258d --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/cmd_runner/action_plugins/_unsafe_assert.py @@ -0,0 +1,56 @@ +# Copyright 2012, Dag Wieers <dag@wieers.com> +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.errors import AnsibleError +from ansible.playbook.conditional import Conditional +from ansible.plugins.action import ActionBase + + +class ActionModule(ActionBase): + ''' Fail with custom message ''' + + _requires_connection = False + + _VALID_ARGS = frozenset(('msg', 'that')) + + def _make_safe(self, text): + # A simple str(text) won't do it since AnsibleUnsafeText is clever :-) + return ''.join(chr(ord(x)) for x in text) + + def run(self, tmp=None, task_vars=None): + if task_vars is None: + task_vars = dict() + + result = super(ActionModule, self).run(tmp, task_vars) + del tmp # tmp no longer has any effect + + if 'that' not in self._task.args: + raise AnsibleError('conditional required in "that" string') + + fail_msg = 'Assertion failed' + success_msg = 'All assertions passed' + + thats = self._task.args['that'] + + cond = Conditional(loader=self._loader) + result['_ansible_verbose_always'] = True + + for that in thats: + cond.when = [str(self._make_safe(that))] + test_result = cond.evaluate_conditional(templar=self._templar, all_vars=task_vars) + if not test_result: + result['failed'] = True + result['evaluated_to'] = test_result + result['assertion'] = that + + result['msg'] = fail_msg + + return result + + result['changed'] = False + result['msg'] = success_msg + return result diff --git a/ansible_collections/community/general/tests/integration/targets/cmd_runner/library/cmd_echo.py b/ansible_collections/community/general/tests/integration/targets/cmd_runner/library/cmd_echo.py index cd8766264..ec0beb98e 100644 --- a/ansible_collections/community/general/tests/integration/targets/cmd_runner/library/cmd_echo.py +++ b/ansible_collections/community/general/tests/integration/targets/cmd_runner/library/cmd_echo.py @@ -21,11 +21,14 @@ 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={}), check_mode_skip=dict(type="bool", default=False), aa=dict(type="raw"), + tt=dict(), ), supports_check_mode=True, ) @@ -40,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']) diff --git a/ansible_collections/community/general/tests/integration/targets/cmd_runner/meta/main.yml b/ansible_collections/community/general/tests/integration/targets/cmd_runner/meta/main.yml new file mode 100644 index 000000000..982de6eb0 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/cmd_runner/meta/main.yml @@ -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 diff --git a/ansible_collections/community/general/tests/integration/targets/cmd_runner/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/cmd_runner/tasks/main.yml index 36ab039f0..e955c5d3d 100644 --- a/ansible_collections/community/general/tests/integration/targets/cmd_runner/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/cmd_runner/tasks/main.yml @@ -6,3 +6,4 @@ ansible.builtin.include_tasks: file: test_cmd_echo.yml loop: "{{ cmd_echo_tests }}" + when: item.condition | default(true) | bool diff --git a/ansible_collections/community/general/tests/integration/targets/cmd_runner/tasks/test_cmd_echo.yml b/ansible_collections/community/general/tests/integration/targets/cmd_runner/tasks/test_cmd_echo.yml index 1c2caf2b5..a2a9fb8b7 100644 --- a/ansible_collections/community/general/tests/integration/targets/cmd_runner/tasks/test_cmd_echo.yml +++ b/ansible_collections/community/general/tests/integration/targets/cmd_runner/tasks/test_cmd_echo.yml @@ -3,17 +3,27 @@ # 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 }}] +- name: create copy of /bin/echo ({{ item.name }}) + ansible.builtin.copy: + src: /bin/echo + dest: "{{ item.copy_to }}/echo" + mode: "0755" + remote_src: true + when: item.copy_to is defined + +- name: test cmd_echo module ({{ item.name }}) cmd_echo: - arg_formats: "{{ item.arg_formats|default(omit) }}" + 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) }}" + 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) }}" + check_mode: "{{ item.check_mode | default(omit) }}" + ignore_errors: "{{ item.expect_error | default(omit) }}" -- name: check results [{{ item.name }}] - assert: +- name: check results ({{ item.name }}) + _unsafe_assert: that: "{{ item.assertions }}" diff --git a/ansible_collections/community/general/tests/integration/targets/cmd_runner/vars/main.yml b/ansible_collections/community/general/tests/integration/targets/cmd_runner/vars/main.yml index 7f0027d49..f9a715338 100644 --- a/ansible_collections/community/general/tests/integration/targets/cmd_runner/vars/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/cmd_runner/vars/main.yml @@ -121,3 +121,142 @@ cmd_echo_tests: - test_result.rc == None - test_result.out == None - test_result.err == None + + - name: set aa and tt value + arg_formats: + aa: + func: as_opt_eq_val + args: [--answer] + tt: + func: as_opt_val + args: [--tt-arg] + arg_order: 'aa tt' + arg_values: + tt: potatoes + aa: 11 + assertions: + - test_result.rc == 0 + - test_result.out == "-- --answer=11 --tt-arg potatoes\n" + - test_result.err == "" + + - 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 diff --git a/ansible_collections/community/general/tests/integration/targets/connection_incus/aliases b/ansible_collections/community/general/tests/integration/targets/connection_incus/aliases new file mode 100644 index 000000000..5a0c47032 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/connection_incus/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +non_local +unsupported diff --git a/ansible_collections/community/general/tests/integration/targets/connection_incus/runme.sh b/ansible_collections/community/general/tests/integration/targets/connection_incus/runme.sh new file mode 100755 index 000000000..9f31da64d --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/connection_incus/runme.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# 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 + +set -eux + +# Connection tests for POSIX platforms use this script by linking to it from the appropriate 'connection_' target dir. +# The name of the inventory group to test is extracted from the directory name following the 'connection_' prefix. + +group=$(python -c \ + "from os import path; print(path.basename(path.abspath(path.dirname('$0'))).replace('connection_', ''))") + +cd ../connection + +INVENTORY="../connection_${group}/test_connection.inventory" ./test.sh \ + -e target_hosts="${group}" \ + -e action_prefix= \ + -e local_tmp=/tmp/ansible-local \ + -e remote_tmp=/tmp/ansible-remote \ + "$@" diff --git a/ansible_collections/community/general/tests/integration/targets/connection_incus/test_connection.inventory b/ansible_collections/community/general/tests/integration/targets/connection_incus/test_connection.inventory new file mode 100644 index 000000000..84b69faf7 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/connection_incus/test_connection.inventory @@ -0,0 +1,11 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +[incus] +incus-pipelining ansible_ssh_pipelining=true +incus-no-pipelining ansible_ssh_pipelining=false +[incus:vars] +ansible_host=ubuntu-2204 +ansible_connection=community.general.incus +ansible_python_interpreter=python3 diff --git a/ansible_collections/community/general/tests/integration/targets/consul/tasks/consul_auth_method.yml b/ansible_collections/community/general/tests/integration/targets/consul/tasks/consul_auth_method.yml new file mode 100644 index 000000000..611d67309 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/consul/tasks/consul_auth_method.yml @@ -0,0 +1,74 @@ +--- +# Copyright (c) 2024, Florian Apolloner (@apollo13) +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Create an auth method + community.general.consul_auth_method: + name: test + type: jwt + config: + jwt_validation_pubkeys: + - | + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo + 4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u + +qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh + kd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ + 0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg + cKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc + mwIDAQAB + -----END PUBLIC KEY----- + register: result + +- assert: + that: + - result is changed + - result.auth_method.Type == 'jwt' + - result.operation == 'create' + +- name: Update auth method + community.general.consul_auth_method: + name: test + max_token_ttl: 30m80s + register: result + +- assert: + that: + - result is changed + - result.auth_method.Type == 'jwt' + - result.operation == 'update' + +- name: Update auth method (noop) + community.general.consul_auth_method: + name: test + max_token_ttl: 30m80s + register: result + +- assert: + that: + - result is not changed + - result.auth_method.Type == 'jwt' + - result.operation is not defined + +- name: Delete auth method + community.general.consul_auth_method: + name: test + state: absent + register: result + +- assert: + that: + - result is changed + - result.operation == 'remove' + +- name: Delete auth method (noop) + community.general.consul_auth_method: + name: test + state: absent + register: result + +- assert: + that: + - result is not changed + - result.operation is not defined diff --git a/ansible_collections/community/general/tests/integration/targets/consul/tasks/consul_binding_rule.yml b/ansible_collections/community/general/tests/integration/targets/consul/tasks/consul_binding_rule.yml new file mode 100644 index 000000000..218daf982 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/consul/tasks/consul_binding_rule.yml @@ -0,0 +1,73 @@ +--- +# Copyright (c) 2024, Florian Apolloner (@apollo13) +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Create an auth method + community.general.consul_auth_method: + name: test + type: jwt + config: + jwt_validation_pubkeys: + - | + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo + 4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u + +qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh + kd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ + 0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg + cKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc + mwIDAQAB + -----END PUBLIC KEY----- + +- name: Create a binding rule + community.general.consul_binding_rule: + name: test-binding + description: my description + auth_method: test + bind_type: service + bind_name: yolo + register: result + +- assert: + that: + - result is changed + - result.binding_rule.AuthMethod == 'test' + - result.binding.Description == 'test-binding: my description' + - result.operation == 'create' + +- name: Update a binding rule + community.general.consul_binding_rule: + name: test-binding + auth_method: test + bind_name: yolo2 + register: result + +- assert: + that: + - result is changed + - result.binding.Description == 'test-binding: my description' + - result.operation == 'update' + +- name: Update a binding rule (noop) + community.general.consul_binding_rule: + name: test-binding + auth_method: test + register: result + +- assert: + that: + - result is not changed + - result.binding.Description == 'test-binding: my description' + - result.operation is not defined + +- name: Delete a binding rule + community.general.consul_binding_rule: + name: test-binding + auth_method: test + state: absent + register: result +- assert: + that: + - result is changed + - result.operation == 'remove'
\ No newline at end of file diff --git a/ansible_collections/community/general/tests/integration/targets/consul/tasks/consul_general.yml b/ansible_collections/community/general/tests/integration/targets/consul/tasks/consul_general.yml new file mode 100644 index 000000000..2fc28efc2 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/consul/tasks/consul_general.yml @@ -0,0 +1,76 @@ +--- +# 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: ensure unknown scheme fails + consul_session: + state: info + id: dummy + scheme: non_existent + token: "{{ consul_management_token }}" + register: result + ignore_errors: true + +- assert: + that: + - result is failed + +- name: ensure SSL certificate is checked + consul_session: + state: info + id: dummy + port: 8501 + scheme: https + token: "{{ consul_management_token }}" + register: result + ignore_errors: true + +- name: previous task should fail since certificate is not known + assert: + that: + - result is failed + - "'certificate verify failed' in result.msg" + +- name: ensure SSL certificate isn't checked when validate_certs is disabled + consul_session: + state: info + id: dummy + port: 8501 + scheme: https + token: "{{ consul_management_token }}" + validate_certs: false + register: result + +- name: previous task should succeed since certificate isn't checked + assert: + that: + - result is changed + +- name: ensure a secure connection is possible + consul_session: + state: info + id: dummy + port: 8501 + scheme: https + token: "{{ consul_management_token }}" + ca_path: '{{ remote_dir }}/cert.pem' + register: result + +- assert: + that: + - result is changed + +- name: ensure connection errors are handled properly + consul_session: + state: info + id: dummy + token: "{{ consul_management_token }}" + port: 1234 + register: result + ignore_errors: true + +- assert: + that: + - result is failed + - result.msg.startswith('Could not connect to consul agent at localhost:1234, error was') diff --git a/ansible_collections/community/general/tests/integration/targets/consul/tasks/consul_kv.yml b/ansible_collections/community/general/tests/integration/targets/consul/tasks/consul_kv.yml new file mode 100644 index 000000000..6cca73137 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/consul/tasks/consul_kv.yml @@ -0,0 +1,57 @@ +--- +# Copyright (c) 2024, Florian Apolloner (@apollo13) +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Create a key + consul_kv: + key: somekey + value: somevalue + token: "{{ consul_management_token }}" + register: result + +- assert: + that: + - result is changed + - result.data.Value == 'somevalue' + +#- name: Test the lookup +# assert: +# that: +# - lookup('community.general.consul_kv', 'somekey', token=consul_management_token) == 'somevalue' + +- name: Update a key with the same data + consul_kv: + key: somekey + value: somevalue + token: "{{ consul_management_token }}" + register: result + +- assert: + that: + - result is not changed + - result.data.Value == 'somevalue' + +- name: Remove a key from the store + consul_kv: + key: somekey + state: absent + token: "{{ consul_management_token }}" + register: result + +- assert: + that: + - result is changed + - result.data.Value == 'somevalue' + +- name: Remove a non-existant key from the store + consul_kv: + key: somekey + state: absent + token: "{{ consul_management_token }}" + register: result + +- assert: + that: + - result is not changed + - not result.data
\ No newline at end of file diff --git a/ansible_collections/community/general/tests/integration/targets/consul/tasks/consul_policy.yml b/ansible_collections/community/general/tests/integration/targets/consul/tasks/consul_policy.yml new file mode 100644 index 000000000..336324f03 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/consul/tasks/consul_policy.yml @@ -0,0 +1,72 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Create a policy with rules + consul_policy: + name: foo-access + rules: | + key "foo" { + policy = "read" + } + key "private/foo" { + policy = "deny" + } + register: result + +- assert: + that: + - result is changed + - result.policy.Name == 'foo-access' + - result.operation == 'create' + +- name: Update the rules associated to a policy + consul_policy: + name: foo-access + rules: | + key "foo" { + policy = "read" + } + key "private/foo" { + policy = "deny" + } + event "bbq" { + policy = "write" + } + register: result + +- assert: + that: + - result is changed + - result.operation == 'update' + +- name: Update reports not changed when updating again without changes + consul_policy: + name: foo-access + rules: | + key "foo" { + policy = "read" + } + key "private/foo" { + policy = "deny" + } + event "bbq" { + policy = "write" + } + register: result + +- assert: + that: + - result is not changed + - result.operation is not defined + +- name: Remove a policy + consul_policy: + name: foo-access + state: absent + register: result +- assert: + that: + - result is changed + - result.operation == 'remove'
\ No newline at end of file diff --git a/ansible_collections/community/general/tests/integration/targets/consul/tasks/consul_role.yml b/ansible_collections/community/general/tests/integration/targets/consul/tasks/consul_role.yml new file mode 100644 index 000000000..9b0504e0b --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/consul/tasks/consul_role.yml @@ -0,0 +1,194 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Create a policy with rules + consul_policy: + name: foo-access-for-role + rules: | + key "foo" { + policy = "read" + } + key "private/foo" { + policy = "deny" + } + register: policy_result + +- name: Create another policy with rules + consul_policy: + name: bar-access-for-role + rules: | + key "bar" { + policy = "read" + } + key "private/bar" { + policy = "deny" + } + register: policy_result + +- name: Create a role with policy + consul_role: + name: foo-role-with-policy + policies: + - name: "foo-access-for-role" + register: result + +- assert: + that: + - result is changed + - result.role.Name == 'foo-role-with-policy' + - result.operation == 'create' + +- name: Update policy description, in check mode + consul_role: + name: foo-role-with-policy + description: "Testing updating description" + check_mode: yes + register: result + +- assert: + that: + - result is changed + - result.role.Description == "Testing updating description" + - result.role.Policies.0.Name == 'foo-access-for-role' + - result.operation == 'update' + +- name: Update policy to add the description + consul_role: + name: foo-role-with-policy + description: "Role for testing policies" + register: result + +- assert: + that: + - result is changed + - result.role.Description == "Role for testing policies" + - result.role.Policies.0.Name == 'foo-access-for-role' + - result.operation == 'update' + +- name: Update the role with another policy, also testing leaving description blank + consul_role: + name: foo-role-with-policy + policies: + - name: "foo-access-for-role" + - name: "bar-access-for-role" + register: result + +- assert: + that: + - result is changed + - result.role.Policies.0.Name == 'foo-access-for-role' + - result.role.Policies.1.Name == 'bar-access-for-role' + - result.role.Description == "Role for testing policies" + - result.operation == 'update' + +- name: Create a role with service identity + consul_role: + name: role-with-service-identity + service_identities: + - name: web + datacenters: + - dc1 + register: result + +- assert: + that: + - result is changed + - result.role.ServiceIdentities.0.ServiceName == "web" + - result.role.ServiceIdentities.0.Datacenters.0 == "dc1" + +- name: Update the role with service identity in check mode + consul_role: + name: role-with-service-identity + service_identities: + - name: web + datacenters: + - dc2 + register: result + check_mode: yes + +- assert: + that: + - result is changed + - result.role.ServiceIdentities.0.ServiceName == "web" + - result.role.ServiceIdentities.0.Datacenters.0 == "dc2" + +- name: Update the role with service identity to add a policy, leaving the service id unchanged + consul_role: + name: role-with-service-identity + policies: + - name: "foo-access-for-role" + register: result + +- assert: + that: + - result is changed + - result.role.ServiceIdentities.0.ServiceName == "web" + - result.role.ServiceIdentities.0.Datacenters.0 == "dc1" + - result.role.Policies.0.Name == 'foo-access-for-role' + +- name: Update the role with service identity to remove the policies + consul_role: + name: role-with-service-identity + policies: [] + register: result + +- assert: + that: + - result is changed + - result.role.ServiceIdentities.0.ServiceName == "web" + - result.role.ServiceIdentities.0.Datacenters.0 == "dc1" + - result.role.Policies is not defined + +- name: Update the role with service identity to remove the node identities, in check mode + consul_role: + name: role-with-service-identity + node_identities: [] + register: result + check_mode: yes + +- assert: + that: + - result is changed + - result.role.ServiceIdentities.0.ServiceName == "web" + - result.role.ServiceIdentities.0.Datacenters.0 == "dc1" + - result.role.Policies is not defined + - result.role.NodeIdentities == [] # in check mode the cleared field is returned as an empty array + +- name: Update the role with service identity to remove the service identities + consul_role: + name: role-with-service-identity + service_identities: [] + register: result + +- assert: + that: + - result is changed + - result.role.ServiceIdentities is not defined # in normal mode the dictionary is removed from the result + - result.role.Policies is not defined + +- name: Create a role with node identity + consul_role: + name: role-with-node-identity + node_identities: + - name: node-1 + datacenter: dc2 + register: result + +- assert: + that: + - result is changed + - result.role.NodeIdentities.0.NodeName == "node-1" + - result.role.NodeIdentities.0.Datacenter == "dc2" + +- name: Remove the last role + consul_role: + name: role-with-node-identity + state: absent + register: result + +- assert: + that: + - result is changed + - result.operation == 'remove'
\ No newline at end of file diff --git a/ansible_collections/community/general/tests/integration/targets/consul/tasks/consul_session.yml b/ansible_collections/community/general/tests/integration/targets/consul/tasks/consul_session.yml index 543668964..7f852a36d 100644 --- a/ansible_collections/community/general/tests/integration/targets/consul/tasks/consul_session.yml +++ b/ansible_collections/community/general/tests/integration/targets/consul/tasks/consul_session.yml @@ -52,7 +52,7 @@ - name: ensure session was created assert: that: - - test_session_found|default(False) + - test_session_found|default(false) - name: fetch info about a session consul_session: @@ -75,61 +75,6 @@ that: - result is failed -- name: ensure unknown scheme fails - consul_session: - state: info - id: '{{ session_id }}' - scheme: non_existent - register: result - ignore_errors: true - -- assert: - that: - - result is failed - -- name: ensure SSL certificate is checked - consul_session: - state: info - id: '{{ session_id }}' - port: 8501 - scheme: https - register: result - ignore_errors: true - -- name: previous task should fail since certificate is not known - assert: - that: - - result is failed - - "'certificate verify failed' in result.msg" - -- name: ensure SSL certificate isn't checked when validate_certs is disabled - consul_session: - state: info - id: '{{ session_id }}' - port: 8501 - scheme: https - validate_certs: false - register: result - -- name: previous task should succeed since certificate isn't checked - assert: - that: - - result is changed - -- name: ensure a secure connection is possible - consul_session: - state: info - id: '{{ session_id }}' - port: 8501 - scheme: https - environment: - REQUESTS_CA_BUNDLE: '{{ remote_dir }}/cert.pem' - register: result - -- assert: - that: - - result is changed - - name: delete a session consul_session: state: absent diff --git a/ansible_collections/community/general/tests/integration/targets/consul/tasks/consul_token.yml b/ansible_collections/community/general/tests/integration/targets/consul/tasks/consul_token.yml new file mode 100644 index 000000000..9b3679ef1 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/consul/tasks/consul_token.yml @@ -0,0 +1,88 @@ +--- +# Copyright (c) 2024, Florian Apolloner (@apollo13) +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Create a policy with rules + community.general.consul_policy: + name: "{{ item }}" + rules: | + key "foo" { + policy = "read" + } + loop: + - foo-access + - foo-access2 + +- name: Create token without accessor + community.general.consul_token: + state: present + register: simple_create_result + +- assert: + that: + - simple_create_result is changed + - simple_create_result.token.AccessorID + - simple_create_result.operation == 'create' + +- name: Create token + community.general.consul_token: + state: present + accessor_id: 07a7de84-c9c7-448a-99cc-beaf682efd21 + service_identities: + - service_name: test + datacenters: [test1, test2] + node_identities: + - node_name: test + datacenter: test + policies: + - name: foo-access + - name: foo-access2 + expiration_ttl: 1h + register: create_result + +- assert: + that: + - create_result is changed + - create_result.token.AccessorID == "07a7de84-c9c7-448a-99cc-beaf682efd21" + - create_result.operation == 'create' + +- name: Update token + community.general.consul_token: + state: present + accessor_id: 07a7de84-c9c7-448a-99cc-beaf682efd21 + description: Testing + policies: + - id: "{{ create_result.token.Policies[-1].ID }}" + service_identities: [] + register: result + +- assert: + that: + - result is changed + - result.operation == 'update' + +- name: Update token (noop) + community.general.consul_token: + state: present + accessor_id: 07a7de84-c9c7-448a-99cc-beaf682efd21 + policies: + - id: "{{ create_result.token.Policies[-1].ID }}" + register: result + +- assert: + that: + - result is not changed + - result.operation is not defined + +- name: Remove token + community.general.consul_token: + state: absent + accessor_id: 07a7de84-c9c7-448a-99cc-beaf682efd21 + register: result + +- assert: + that: + - result is changed + - not result.token + - result.operation == 'remove' diff --git a/ansible_collections/community/general/tests/integration/targets/consul/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/consul/tasks/main.yml index a2b63ac95..6fef2b998 100644 --- a/ansible_collections/community/general/tests/integration/targets/consul/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/consul/tasks/main.yml @@ -10,8 +10,8 @@ - name: Install Consul and test vars: - consul_version: 1.5.0 - consul_uri: https://s3.amazonaws.com/ansible-ci-files/test/integration/targets/consul/consul_{{ consul_version }}_{{ ansible_system | lower }}_{{ consul_arch }}.zip + consul_version: 1.13.2 + consul_uri: https://releases.hashicorp.com/consul/{{ consul_version }}/consul_{{ consul_version }}_{{ ansible_system | lower }}_{{ consul_arch }}.zip consul_cmd: '{{ remote_tmp_dir }}/consul' block: - name: Install requests<2.20 (CentOS/RHEL 6) @@ -76,14 +76,32 @@ dest: '{{ remote_tmp_dir }}/consul_config.hcl' - name: Start Consul (dev mode enabled) shell: nohup {{ consul_cmd }} agent -dev -config-file {{ remote_tmp_dir }}/consul_config.hcl </dev/null >/dev/null 2>&1 & + - name: Bootstrap ACL + consul_acl_bootstrap: + register: consul_bootstrap_result + - set_fact: + consul_management_token: '{{ consul_bootstrap_result.result.SecretID }}' - name: Create some data - command: '{{ consul_cmd }} kv put data/value{{ item }} foo{{ item }}' + command: '{{ consul_cmd }} kv put -token={{consul_management_token}} data/value{{ item }} foo{{ item }}' loop: - 1 - 2 - 3 - - import_tasks: consul_session.yml + - import_tasks: consul_general.yml + - import_tasks: consul_kv.yml + + - block: + - import_tasks: consul_session.yml + - import_tasks: consul_policy.yml + - import_tasks: consul_role.yml + - import_tasks: consul_token.yml + - import_tasks: consul_auth_method.yml + - import_tasks: consul_binding_rule.yml + module_defaults: + group/community.general.consul: + token: "{{ consul_management_token }}" + always: - name: Kill consul process shell: kill $(cat {{ remote_tmp_dir }}/consul.pid) - ignore_errors: true + ignore_errors: true
\ No newline at end of file diff --git a/ansible_collections/community/general/tests/integration/targets/consul/templates/consul_config.hcl.j2 b/ansible_collections/community/general/tests/integration/targets/consul/templates/consul_config.hcl.j2 index 96da5d664..91bfb08ae 100644 --- a/ansible_collections/community/general/tests/integration/targets/consul/templates/consul_config.hcl.j2 +++ b/ansible_collections/community/general/tests/integration/targets/consul/templates/consul_config.hcl.j2 @@ -12,3 +12,8 @@ ports { } key_file = "{{ remote_dir }}/privatekey.pem" cert_file = "{{ remote_dir }}/cert.pem" +acl { + enabled = true + default_policy = "deny" + down_policy = "extend-cache" +} diff --git a/ansible_collections/community/general/tests/integration/targets/copr/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/copr/tasks/main.yml index 0e4651724..0d6637811 100644 --- a/ansible_collections/community/general/tests/integration/targets/copr/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/copr/tasks/main.yml @@ -10,12 +10,6 @@ ansible_distribution == 'Fedora' or (ansible_os_family == 'RedHat' and ansible_distribution != 'Fedora' and ansible_distribution_major_version | int >= 8) - # The copr module imports dnf which is only available for the system Python - # interpreter. - - > - not (ansible_distribution == 'CentOS' and - ansible_distribution_major_version | int == 8 and not - ansible_python_version.startswith('3.6')) block: - debug: var=copr_chroot - name: enable copr project diff --git a/ansible_collections/community/general/tests/integration/targets/deploy_helper/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/deploy_helper/tasks/main.yml index fdd8bd87b..9bd5f4150 100644 --- a/ansible_collections/community/general/tests/integration/targets/deploy_helper/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/deploy_helper/tasks/main.yml @@ -17,25 +17,25 @@ assert: that: - "'project_path' in deploy_helper" - - "deploy_helper.current_path == '{{ deploy_helper.project_path }}/current'" - - "deploy_helper.releases_path == '{{ deploy_helper.project_path }}/releases'" - - "deploy_helper.shared_path == '{{ deploy_helper.project_path }}/shared'" + - "deploy_helper.current_path == deploy_helper.project_path ~ '/current'" + - "deploy_helper.releases_path == deploy_helper.project_path ~ '/releases'" + - "deploy_helper.shared_path == deploy_helper.project_path ~ '/shared'" - "deploy_helper.unfinished_filename == 'DEPLOY_UNFINISHED'" - "'previous_release' in deploy_helper" - "'previous_release_path' in deploy_helper" - "'new_release' in deploy_helper" - "'new_release_path' in deploy_helper" - - "deploy_helper.new_release_path == '{{ deploy_helper.releases_path }}/{{ deploy_helper.new_release }}'" + - "deploy_helper.new_release_path == deploy_helper.releases_path ~ '/' ~ deploy_helper.new_release" - name: State=query with relative overridden paths deploy_helper: path={{ deploy_helper_test_root }} current_path=CURRENT_PATH releases_path=RELEASES_PATH shared_path=SHARED_PATH state=query - name: Assert State=query with relative overridden paths assert: that: - - "deploy_helper.current_path == '{{ deploy_helper.project_path }}/CURRENT_PATH'" - - "deploy_helper.releases_path == '{{ deploy_helper.project_path }}/RELEASES_PATH'" - - "deploy_helper.shared_path == '{{ deploy_helper.project_path }}/SHARED_PATH'" - - "deploy_helper.new_release_path == '{{ deploy_helper.releases_path }}/{{ deploy_helper.new_release}}'" + - "deploy_helper.current_path == deploy_helper.project_path ~ '/CURRENT_PATH'" + - "deploy_helper.releases_path == deploy_helper.project_path ~ '/RELEASES_PATH'" + - "deploy_helper.shared_path == deploy_helper.project_path ~ '/SHARED_PATH'" + - "deploy_helper.new_release_path == deploy_helper.releases_path ~ '/' ~ deploy_helper.new_release" - name: State=query with absolute overridden paths deploy_helper: path={{ deploy_helper_test_root }} current_path=/CURRENT_PATH releases_path=/RELEASES_PATH shared_path=/SHARED_PATH state=query @@ -45,7 +45,7 @@ - "deploy_helper.current_path == '/CURRENT_PATH'" - "deploy_helper.releases_path == '/RELEASES_PATH'" - "deploy_helper.shared_path == '/SHARED_PATH'" - - "deploy_helper.new_release_path == '{{ deploy_helper.releases_path }}/{{ deploy_helper.new_release}}'" + - "deploy_helper.new_release_path == deploy_helper.releases_path ~ '/' ~ deploy_helper.new_release" - name: State=query with overridden unfinished_filename deploy_helper: path={{ deploy_helper_test_root }} unfinished_filename=UNFINISHED_DEPLOY state=query diff --git a/ansible_collections/community/general/tests/integration/targets/django_manage/aliases b/ansible_collections/community/general/tests/integration/targets/django_manage/aliases index 98aed9e9d..979054916 100644 --- a/ansible_collections/community/general/tests/integration/targets/django_manage/aliases +++ b/ansible_collections/community/general/tests/integration/targets/django_manage/aliases @@ -11,5 +11,10 @@ skip/rhel8.2 skip/rhel8.3 skip/rhel8.4 skip/rhel8.5 +skip/rhel8.6 +skip/rhel8.7 +skip/rhel8.8 skip/rhel9.0 skip/rhel9.1 +skip/rhel9.2 +skip/rhel9.3 diff --git a/ansible_collections/community/general/tests/integration/targets/ejabberd_user/aliases b/ansible_collections/community/general/tests/integration/targets/ejabberd_user/aliases new file mode 100644 index 000000000..11c37f6bb --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/ejabberd_user/aliases @@ -0,0 +1,11 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/posix/3 +skip/osx +skip/macos +skip/freebsd +skip/alpine +skip/rhel +destructive diff --git a/ansible_collections/community/general/tests/integration/targets/ejabberd_user/handlers/main.yml b/ansible_collections/community/general/tests/integration/targets/ejabberd_user/handlers/main.yml new file mode 100644 index 000000000..16eed4733 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/ejabberd_user/handlers/main.yml @@ -0,0 +1,9 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +--- +- name: Remove ejabberd + ansible.builtin.package: + name: ejabberd + state: absent diff --git a/ansible_collections/community/general/tests/integration/targets/ejabberd_user/meta/main.yml b/ansible_collections/community/general/tests/integration/targets/ejabberd_user/meta/main.yml new file mode 100644 index 000000000..2fcd152f9 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/ejabberd_user/meta/main.yml @@ -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_pkg_mgr diff --git a/ansible_collections/community/general/tests/integration/targets/ejabberd_user/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/ejabberd_user/tasks/main.yml new file mode 100644 index 000000000..33e07b785 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/ejabberd_user/tasks/main.yml @@ -0,0 +1,122 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# 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: Bail out if not supported + ansible.builtin.meta: end_play + when: ansible_distribution in ('Alpine', 'openSUSE Leap', 'CentOS', 'Fedora') + + +- name: Remove ejabberd + ansible.builtin.package: + name: ejabberd + state: absent + +- name: Create user without ejabberdctl installed + community.general.ejabberd_user: + host: localhost + username: alice + password: pa$$w0rd + state: present + register: user_no_ejabberdctl + ignore_errors: true + +- name: Install ejabberd + ansible.builtin.package: + name: ejabberd + state: present + notify: Remove ejabberd + +- name: Make runnable on Arch + community.general.ini_file: + path: /usr/lib/systemd/system/ejabberd.service + section: Service + option: "{{ item }}" + state: absent + loop: + - PrivateDevices + - AmbientCapabilities + when: ansible_distribution == 'Archlinux' + +- name: Make installable on Arch + systemd: + daemon_reload: true + when: ansible_distribution == 'Archlinux' + +- ansible.builtin.service: + name: ejabberd + state: started + +- name: Create user alice (check) + community.general.ejabberd_user: + host: localhost + username: alice + password: pa$$w0rd + state: present + check_mode: true + register: user_alice_check + +- name: Create user alice + community.general.ejabberd_user: + host: localhost + username: alice + password: pa$$w0rd + state: present + register: user_alice + +- name: Create user alice (idempotency) + community.general.ejabberd_user: + host: localhost + username: alice + password: pa$$w0rd + state: present + register: user_alice_idempot + +- name: Create user alice (change password) + community.general.ejabberd_user: + host: localhost + username: alice + password: different_pa$$w0rd + state: present + register: user_alice_chgpw + +- name: Remove user alice (check) + community.general.ejabberd_user: + host: localhost + username: alice + state: absent + register: remove_alice_check + check_mode: true + +- name: Remove user alice + community.general.ejabberd_user: + host: localhost + username: alice + state: absent + register: remove_alice + +- name: Remove user alice (idempotency) + community.general.ejabberd_user: + host: localhost + username: alice + state: absent + register: remove_alice_idempot + +- name: Assertions + ansible.builtin.assert: + that: + - user_no_ejabberdctl is failed + - "'Failed to find required executable' in user_no_ejabberdctl.msg" + - user_alice_check is changed + - user_alice is changed + - user_alice_idempot is not changed + - user_alice_chgpw is changed + - remove_alice_check is changed + - remove_alice is changed + - remove_alice_idempot is not changed diff --git a/ansible_collections/community/general/tests/integration/targets/etcd3/aliases b/ansible_collections/community/general/tests/integration/targets/etcd3/aliases index 264446580..78b1fff07 100644 --- a/ansible_collections/community/general/tests/integration/targets/etcd3/aliases +++ b/ansible_collections/community/general/tests/integration/targets/etcd3/aliases @@ -8,5 +8,4 @@ skip/aix skip/osx skip/macos skip/freebsd -skip/python2.6 # installing etcd3 python module will fail on python < 2.7 disabled # see https://github.com/ansible-collections/community.general/issues/322 diff --git a/ansible_collections/community/general/tests/integration/targets/filesize/tasks/sparse.yml b/ansible_collections/community/general/tests/integration/targets/filesize/tasks/sparse.yml index 79145b6e2..348a1eea1 100644 --- a/ansible_collections/community/general/tests/integration/targets/filesize/tasks/sparse.yml +++ b/ansible_collections/community/general/tests/integration/targets/filesize/tasks/sparse.yml @@ -5,10 +5,10 @@ # Test module with sparse files -- name: Create a huge sparse file of 4TB (check mode) +- name: Create a huge sparse file of 2TB (check mode) community.general.filesize: path: "{{ filesize_testfile }}" - size: 4TB + size: 2TB sparse: true register: filesize_test_sparse_01 check_mode: true @@ -20,10 +20,10 @@ register: filesize_stat_sparse_01 -- name: Create a huge sparse file of 4TB +- name: Create a huge sparse file of 2TB community.general.filesize: path: "{{ filesize_testfile }}" - size: 4TB + size: 2TB sparse: true register: filesize_test_sparse_02 @@ -34,34 +34,34 @@ register: filesize_stat_sparse_02 -- name: Create a huge sparse file of 4TB (4000GB) (check mode, idempotency) +- name: Create a huge sparse file of 2TB (2000GB) (check mode, idempotency) community.general.filesize: path: "{{ filesize_testfile }}" - size: 4000GB + size: 2000GB sparse: true register: filesize_test_sparse_03 check_mode: true -- name: Create a huge sparse file of 4TB (4000GB) (idempotency) +- name: Create a huge sparse file of 2TB (2000GB) (idempotency) community.general.filesize: path: "{{ filesize_testfile }}" - size: 4000GB + size: 2000GB sparse: true register: filesize_test_sparse_04 -- name: Create a huge sparse file of 4TB (4000000 × 1MB) (check mode, idempotency) +- name: Create a huge sparse file of 2TB (2000000 × 1MB) (check mode, idempotency) community.general.filesize: path: "{{ filesize_testfile }}" - size: 4000000 + size: 2000000 blocksize: 1MB sparse: true register: filesize_test_sparse_05 check_mode: true -- name: Create a huge sparse file of 4TB (4000000 × 1MB) (idempotency) +- name: Create a huge sparse file of 2TB (2000000 × 1MB) (idempotency) community.general.filesize: path: "{{ filesize_testfile }}" - size: 4000000 + size: 2000000 blocksize: 1MB sparse: true register: filesize_test_sparse_06 @@ -89,15 +89,15 @@ - filesize_test_sparse_05.cmd is undefined - filesize_test_sparse_06.cmd is undefined - - filesize_test_sparse_01.filesize.bytes == 4*1000**4 - - filesize_test_sparse_02.filesize.bytes == 4*1000**4 - - filesize_test_sparse_03.filesize.bytes == 4*1000**4 - - filesize_test_sparse_04.filesize.bytes == 4*1000**4 - - filesize_test_sparse_05.filesize.bytes == 4*1000**4 - - filesize_test_sparse_06.filesize.bytes == 4*1000**4 + - filesize_test_sparse_01.filesize.bytes == 2*1000**4 + - filesize_test_sparse_02.filesize.bytes == 2*1000**4 + - filesize_test_sparse_03.filesize.bytes == 2*1000**4 + - filesize_test_sparse_04.filesize.bytes == 2*1000**4 + - filesize_test_sparse_05.filesize.bytes == 2*1000**4 + - filesize_test_sparse_06.filesize.bytes == 2*1000**4 - - filesize_test_sparse_01.size_diff == 4*1000**4 - - filesize_test_sparse_02.size_diff == 4*1000**4 + - filesize_test_sparse_01.size_diff == 2*1000**4 + - filesize_test_sparse_02.size_diff == 2*1000**4 - filesize_test_sparse_03.size_diff == 0 - filesize_test_sparse_04.size_diff == 0 - filesize_test_sparse_05.size_diff == 0 @@ -106,24 +106,24 @@ - filesize_test_sparse_01.state is undefined - filesize_test_sparse_02.state in ["file"] - filesize_test_sparse_01.size is undefined - - filesize_test_sparse_02.size == 4*1000**4 - - filesize_test_sparse_03.size == 4*1000**4 - - filesize_test_sparse_04.size == 4*1000**4 - - filesize_test_sparse_05.size == 4*1000**4 - - filesize_test_sparse_06.size == 4*1000**4 + - filesize_test_sparse_02.size == 2*1000**4 + - filesize_test_sparse_03.size == 2*1000**4 + - filesize_test_sparse_04.size == 2*1000**4 + - filesize_test_sparse_05.size == 2*1000**4 + - filesize_test_sparse_06.size == 2*1000**4 - not filesize_stat_sparse_01.stat.exists - filesize_stat_sparse_02.stat.exists - filesize_stat_sparse_02.stat.isreg - - filesize_stat_sparse_02.stat.size == 4*1000**4 - - filesize_stat_sparse_06.stat.size == 4*1000**4 + - filesize_stat_sparse_02.stat.size == 2*1000**4 + - filesize_stat_sparse_06.stat.size == 2*1000**4 -- name: Change sparse file size to 4TiB (check mode) +- name: Change sparse file size to 2TiB (check mode) community.general.filesize: path: "{{ filesize_testfile }}" - size: 4TiB + size: 2TiB sparse: true register: filesize_test_sparse_11 check_mode: true @@ -135,10 +135,10 @@ register: filesize_stat_sparse_11 -- name: Change sparse file size to 4TiB +- name: Change sparse file size to 2TiB community.general.filesize: path: "{{ filesize_testfile }}" - size: 4TiB + size: 2TiB sparse: true register: filesize_test_sparse_12 @@ -149,18 +149,18 @@ register: filesize_stat_sparse_12 -- name: Change sparse file size to 4TiB (4096GiB) (check mode, idempotency) +- name: Change sparse file size to 2TiB (2048GiB) (check mode, idempotency) community.general.filesize: path: "{{ filesize_testfile }}" - size: 4096GiB + size: 2048GiB sparse: true register: filesize_test_sparse_13 check_mode: true -- name: Change sparse file size to 4TiB (4096GiB) (idempotency) +- name: Change sparse file size to 2TiB (2048GiB) (idempotency) community.general.filesize: path: "{{ filesize_testfile }}" - size: 4096GiB + size: 2048GiB sparse: true register: filesize_test_sparse_14 @@ -183,26 +183,26 @@ - filesize_test_sparse_13.cmd is undefined - filesize_test_sparse_14.cmd is undefined - - filesize_test_sparse_11.size_diff == 398046511104 - - filesize_test_sparse_12.size_diff == 398046511104 + - filesize_test_sparse_11.size_diff == 199023255552 + - filesize_test_sparse_12.size_diff == 199023255552 - filesize_test_sparse_13.size_diff == 0 - filesize_test_sparse_14.size_diff == 0 - - filesize_test_sparse_11.size == 4000000000000 - - filesize_test_sparse_12.size == 4398046511104 - - filesize_test_sparse_13.size == 4398046511104 - - filesize_test_sparse_14.size == 4398046511104 + - filesize_test_sparse_11.size == 2000000000000 + - filesize_test_sparse_12.size == 2199023255552 + - filesize_test_sparse_13.size == 2199023255552 + - filesize_test_sparse_14.size == 2199023255552 - - filesize_stat_sparse_11.stat.size == 4000000000000 - - filesize_stat_sparse_12.stat.size == 4398046511104 - - filesize_stat_sparse_14.stat.size == 4398046511104 + - filesize_stat_sparse_11.stat.size == 2000000000000 + - filesize_stat_sparse_12.stat.size == 2199023255552 + - filesize_stat_sparse_14.stat.size == 2199023255552 -- name: Change sparse file size to 4.321TB (check mode) +- name: Change sparse file size to 2.321TB (check mode) community.general.filesize: path: "{{ filesize_testfile }}" - size: 4.321TB + size: 2.321TB sparse: true register: filesize_test_sparse_21 check_mode: true @@ -214,10 +214,10 @@ register: filesize_stat_sparse_21 -- name: Change sparse file size to 4.321TB +- name: Change sparse file size to 2.321TB community.general.filesize: path: "{{ filesize_testfile }}" - size: 4.321TB + size: 2.321TB sparse: true register: filesize_test_sparse_22 @@ -228,19 +228,19 @@ register: filesize_stat_sparse_22 -- name: Change sparse file size to 4321×1GB (check mode, idempotency) +- name: Change sparse file size to 2321×1GB (check mode, idempotency) community.general.filesize: path: "{{ filesize_testfile }}" - size: 4321 + size: 2321 blocksize: 1GB sparse: true register: filesize_test_sparse_23 check_mode: true -- name: Change sparse file size to 4321×1GB (idempotency) +- name: Change sparse file size to 2321×1GB (idempotency) community.general.filesize: path: "{{ filesize_testfile }}" - size: 4321 + size: 2321 blocksize: 1GB sparse: true register: filesize_test_sparse_24 @@ -264,19 +264,19 @@ - filesize_test_sparse_23.cmd is undefined - filesize_test_sparse_24.cmd is undefined - - filesize_test_sparse_21.size_diff == 4321*1000**3 - 4*1024**4 - - filesize_test_sparse_22.size_diff == 4321*1000**3 - 4*1024**4 + - filesize_test_sparse_21.size_diff == 2321*1000**3 - 2*1024**4 + - filesize_test_sparse_22.size_diff == 2321*1000**3 - 2*1024**4 - filesize_test_sparse_23.size_diff == 0 - filesize_test_sparse_24.size_diff == 0 - - filesize_test_sparse_21.size == 4398046511104 - - filesize_test_sparse_22.size == 4321000000000 - - filesize_test_sparse_23.size == 4321000000000 - - filesize_test_sparse_24.size == 4321000000000 + - filesize_test_sparse_21.size == 2199023255552 + - filesize_test_sparse_22.size == 2321000000000 + - filesize_test_sparse_23.size == 2321000000000 + - filesize_test_sparse_24.size == 2321000000000 - - filesize_stat_sparse_21.stat.size == 4398046511104 - - filesize_stat_sparse_22.stat.size == 4321000000000 - - filesize_stat_sparse_24.stat.size == 4321000000000 + - filesize_stat_sparse_21.stat.size == 2199023255552 + - filesize_stat_sparse_22.stat.size == 2321000000000 + - filesize_stat_sparse_24.stat.size == 2321000000000 diff --git a/ansible_collections/community/general/tests/integration/targets/filesystem/defaults/main.yml b/ansible_collections/community/general/tests/integration/targets/filesystem/defaults/main.yml index 0448d8602..ec446d241 100644 --- a/ansible_collections/community/general/tests/integration/targets/filesystem/defaults/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/filesystem/defaults/main.yml @@ -11,23 +11,23 @@ tested_filesystems: # - XFS: 20Mo # - Btrfs: 150Mo (50Mo when "--metadata single" is used and 100Mb when on newer Fedora versions) # - f2fs: - # - 1.2.0 requires at leat 116Mo + # - 1.2.0 requires at least 116Mo # - 1.7.0 requires at least 30Mo # - 1.10.0 requires at least 38Mo # - resizefs asserts when initial fs is smaller than 60Mo and seems to require 1.10.0 - ext4: {fssize: 10, grow: true} - ext4dev: {fssize: 10, grow: true} - ext3: {fssize: 10, grow: true} - ext2: {fssize: 10, grow: true} - xfs: {fssize: 300, grow: false} # grow requires a mounted filesystem - btrfs: {fssize: 150, grow: false} # grow requires a mounted filesystem - reiserfs: {fssize: 33, grow: false} # grow not implemented - vfat: {fssize: 20, grow: true} - ocfs2: {fssize: '{{ ocfs2_fssize }}', grow: false} # grow not implemented - f2fs: {fssize: '{{ f2fs_fssize|default(60) }}', grow: 'f2fs_version is version("1.10.0", ">=")'} - lvm: {fssize: 20, grow: true} - swap: {fssize: 10, grow: false} # grow not implemented - ufs: {fssize: 10, grow: true} + ext4: {fssize: 10, grow: true, new_uuid: 'random'} + ext4dev: {fssize: 10, grow: true, new_uuid: 'random'} + ext3: {fssize: 10, grow: true, new_uuid: 'random'} + ext2: {fssize: 10, grow: true, new_uuid: 'random'} + xfs: {fssize: 300, grow: false, new_uuid: 'generate'} # grow requires a mounted filesystem + btrfs: {fssize: 150, grow: false, new_uuid: null} # grow requires a mounted filesystem + reiserfs: {fssize: 33, grow: false, new_uuid: null} # grow not implemented + vfat: {fssize: 20, grow: true, new_uuid: null} + ocfs2: {fssize: '{{ ocfs2_fssize }}', grow: false, new_uuid: null} # grow not implemented + f2fs: {fssize: '{{ f2fs_fssize|default(60) }}', grow: 'f2fs_version is version("1.10.0", ">=")', new_uuid: null} + lvm: {fssize: 20, grow: true, new_uuid: 'something'} + swap: {fssize: 10, grow: false, new_uuid: null} # grow not implemented + ufs: {fssize: 10, grow: true, new_uuid: null} get_uuid_any: "blkid -c /dev/null -o value -s UUID {{ dev }}" diff --git a/ansible_collections/community/general/tests/integration/targets/filesystem/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/filesystem/tasks/main.yml index 0ff0f2309..0c15c2155 100644 --- a/ansible_collections/community/general/tests/integration/targets/filesystem/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/filesystem/tasks/main.yml @@ -29,6 +29,7 @@ fstype: '{{ item.0.key }}' fssize: '{{ item.0.value.fssize }}' grow: '{{ item.0.value.grow }}' + new_uuid: '{{ item.0.value.new_uuid }}' action: '{{ item.1 }}' when: # FreeBSD limited support @@ -83,7 +84,7 @@ # TODO: something seems to be broken on Alpine - 'not (ansible_distribution == "Alpine")' - loop: "{{ query('dict', tested_filesystems)|product(['create_fs', 'overwrite_another_fs', 'remove_fs'])|list }}" + loop: "{{ query('dict', tested_filesystems)|product(['create_fs', 'reset_fs_uuid', 'overwrite_another_fs', 'remove_fs', 'set_fs_uuid_on_creation', 'set_fs_uuid_on_creation_with_opts'])|list }}" # With FreeBSD extended support (util-linux is not available before 12.2) diff --git a/ansible_collections/community/general/tests/integration/targets/filesystem/tasks/reset_fs_uuid.yml b/ansible_collections/community/general/tests/integration/targets/filesystem/tasks/reset_fs_uuid.yml new file mode 100644 index 000000000..77dad2203 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/filesystem/tasks/reset_fs_uuid.yml @@ -0,0 +1,59 @@ +--- +# 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 + +# Skip UUID reset tests for FreeBSD due to "xfs_admin: only 'rewrite' supported on V5 fs" +- when: + - new_uuid | default(False) + - not (ansible_system == "FreeBSD" and fstype == "xfs") + block: + - name: "Create filesystem ({{ fstype }})" + community.general.filesystem: + dev: '{{ dev }}' + fstype: '{{ fstype }}' + register: fs_result + + - name: "Get UUID of created filesystem" + ansible.builtin.shell: + cmd: "{{ get_uuid_cmd }}" + changed_when: false + register: uuid + + - name: "Reset filesystem ({{ fstype }}) UUID" + community.general.filesystem: + dev: '{{ dev }}' + fstype: '{{ fstype }}' + uuid: "{{ new_uuid }}" + register: fs_resetuuid_result + + - name: "Get UUID of the filesystem" + ansible.builtin.shell: + cmd: "{{ get_uuid_cmd }}" + changed_when: false + register: uuid2 + + - name: "Assert that filesystem UUID is changed" + ansible.builtin.assert: + that: + - 'fs_resetuuid_result is changed' + - 'fs_resetuuid_result is success' + - 'uuid.stdout != uuid2.stdout' + + - when: + - (grow | bool and (fstype != "vfat" or resize_vfat)) or + (fstype == "xfs" and ansible_system == "Linux" and + ansible_distribution not in ["CentOS", "Ubuntu"]) + block: + - name: "Reset filesystem ({{ fstype }}) UUID and resizefs" + ignore_errors: true + community.general.filesystem: + dev: '{{ dev }}' + fstype: '{{ fstype }}' + uuid: "{{ new_uuid }}" + resizefs: true + register: fs_resetuuid_and_resizefs_result + + - name: "Assert that filesystem UUID reset and resizefs failed" + ansible.builtin.assert: + that: fs_resetuuid_and_resizefs_result is failed diff --git a/ansible_collections/community/general/tests/integration/targets/filesystem/tasks/set_fs_uuid_on_creation.yml b/ansible_collections/community/general/tests/integration/targets/filesystem/tasks/set_fs_uuid_on_creation.yml new file mode 100644 index 000000000..f52c44d65 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/filesystem/tasks/set_fs_uuid_on_creation.yml @@ -0,0 +1,44 @@ +--- +# 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: "Generate a random UUID" + ansible.builtin.set_fact: + random_uuid: '{{ "first_random_uuid" | ansible.builtin.to_uuid }}' + +# Skip UUID set at creation tests for FreeBSD due to "xfs_admin: only 'rewrite' supported on V5 fs" +- when: + - new_uuid | default(False) + - not (ansible_system == "FreeBSD" and fstype == "xfs") + block: + - name: "Create filesystem ({{ fstype }}) with UUID" + community.general.filesystem: + dev: '{{ dev }}' + fstype: '{{ fstype }}' + uuid: '{{ random_uuid }}' + register: fs_result + + - name: "Get UUID of the created filesystem" + ansible.builtin.shell: + cmd: "{{ get_uuid_cmd }}" + changed_when: false + register: uuid + + - name: "Assert that filesystem UUID is the random UUID set on creation" + ansible.builtin.assert: + that: (random_uuid | replace('-','')) == ( uuid.stdout | replace('-','')) + +- when: not (new_uuid | default(False)) + block: + - name: "Create filesystem ({{ fstype }}) without UUID support" + ignore_errors: true + community.general.filesystem: + dev: '{{ dev }}' + fstype: '{{ fstype }}' + uuid: '{{ random_uuid }}' + register: fs_result + + - name: "Assert that filesystem creation failed" + ansible.builtin.assert: + that: fs_result is failed diff --git a/ansible_collections/community/general/tests/integration/targets/filesystem/tasks/set_fs_uuid_on_creation_with_opts.yml b/ansible_collections/community/general/tests/integration/targets/filesystem/tasks/set_fs_uuid_on_creation_with_opts.yml new file mode 100644 index 000000000..fc73e57ee --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/filesystem/tasks/set_fs_uuid_on_creation_with_opts.yml @@ -0,0 +1,33 @@ +--- +# 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 + +# UUID set at creation with opts for XFS is not supported +- when: + - new_uuid | default(False) + - fstype != "xfs" + block: + + - name: "Generate random UUIDs" + ansible.builtin.set_fact: + random_uuid: '{{ "first_random_uuid" | ansible.builtin.to_uuid }}' + random_uuid2: '{{ "second_random_uuid" | ansible.builtin.to_uuid }}' + + - name: "Create filesystem ({{ fstype }}) with fix UUID as opt" + community.general.filesystem: + dev: '{{ dev }}' + fstype: '{{ fstype }}' + opts: "{{ ((fstype == 'lvm') | ansible.builtin.ternary('--norestorefile --uuid ', '-U ')) + random_uuid2 }}" + uuid: '{{ random_uuid }}' + register: fs_result2 + + - name: "Get UUID of the created filesystem" + ansible.builtin.shell: + cmd: "{{ get_uuid_cmd }}" + changed_when: false + register: uuid2 + + - name: "Assert that filesystem UUID is the one set on creation with opt" + ansible.builtin.assert: + that: (random_uuid2 | replace('-','')) == ( uuid2.stdout | replace('-','')) diff --git a/ansible_collections/community/general/tests/integration/targets/filter_counter/aliases b/ansible_collections/community/general/tests/integration/targets/filter_counter/aliases index bc9b4bc99..12d1d6617 100644 --- a/ansible_collections/community/general/tests/integration/targets/filter_counter/aliases +++ b/ansible_collections/community/general/tests/integration/targets/filter_counter/aliases @@ -3,4 +3,3 @@ # SPDX-License-Identifier: GPL-3.0-or-later azp/posix/2 -skip/python2.6 # filters are controller only, and we no longer support Python 2.6 on the controller diff --git a/ansible_collections/community/general/tests/integration/targets/filter_dict/aliases b/ansible_collections/community/general/tests/integration/targets/filter_dict/aliases index e8051e042..343f119da 100644 --- a/ansible_collections/community/general/tests/integration/targets/filter_dict/aliases +++ b/ansible_collections/community/general/tests/integration/targets/filter_dict/aliases @@ -3,4 +3,3 @@ # SPDX-License-Identifier: GPL-3.0-or-later azp/posix/3 -skip/python2.6 # filters are controller only, and we no longer support Python 2.6 on the controller diff --git a/ansible_collections/community/general/tests/integration/targets/filter_dict_kv/aliases b/ansible_collections/community/general/tests/integration/targets/filter_dict_kv/aliases index bc9b4bc99..12d1d6617 100644 --- a/ansible_collections/community/general/tests/integration/targets/filter_dict_kv/aliases +++ b/ansible_collections/community/general/tests/integration/targets/filter_dict_kv/aliases @@ -3,4 +3,3 @@ # SPDX-License-Identifier: GPL-3.0-or-later azp/posix/2 -skip/python2.6 # filters are controller only, and we no longer support Python 2.6 on the controller diff --git a/ansible_collections/community/general/tests/integration/targets/filter_from_csv/aliases b/ansible_collections/community/general/tests/integration/targets/filter_from_csv/aliases index bc9b4bc99..12d1d6617 100644 --- a/ansible_collections/community/general/tests/integration/targets/filter_from_csv/aliases +++ b/ansible_collections/community/general/tests/integration/targets/filter_from_csv/aliases @@ -3,4 +3,3 @@ # SPDX-License-Identifier: GPL-3.0-or-later azp/posix/2 -skip/python2.6 # filters are controller only, and we no longer support Python 2.6 on the controller diff --git a/ansible_collections/community/general/tests/integration/targets/filter_from_ini/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/filter_from_ini/tasks/main.yml new file mode 100644 index 000000000..a2eca36a6 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/filter_from_ini/tasks/main.yml @@ -0,0 +1,60 @@ +--- +# Copyright (c) 2023, Steffen Scheib <steffen@scheib.me> +# 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: 'Define ini_test_dict' + ansible.builtin.set_fact: + ini_test_dict: + section_name: + key_name: 'key value' + + another_section: + connection: 'ssh' + +- name: 'Write INI file that reflects ini_test_dict to {{ ini_test_file }}' + ansible.builtin.copy: + dest: '{{ ini_test_file }}' + content: | + [section_name] + key_name=key value + + [another_section] + connection=ssh + +- name: 'Slurp the test file: {{ ini_test_file }}' + ansible.builtin.slurp: + src: '{{ ini_test_file }}' + register: 'ini_file_content' + +- name: >- + Ensure defined ini_test_dict is the same when retrieved + from {{ ini_test_file }} + ansible.builtin.assert: + that: + - 'ini_file_content.content | b64decode | community.general.from_ini == + ini_test_dict' + +- name: 'Create a file that is not INI formatted: {{ ini_bad_file }}' + ansible.builtin.copy: + dest: '{{ ini_bad_file }}' + content: | + Testing a not INI formatted file. + +- name: 'Slurp the file that is not INI formatted: {{ ini_bad_file }}' + ansible.builtin.slurp: + src: '{{ ini_bad_file }}' + register: 'ini_bad_file_content' + +- name: 'Try parsing the bad file with from_ini: {{ ini_bad_file }}' + ansible.builtin.debug: + var: ini_bad_file_content | b64decode | community.general.from_ini + register: 'ini_bad_file_debug' + ignore_errors: true + +- name: 'Ensure from_ini raised the correct exception' + ansible.builtin.assert: + that: + - "'from_ini failed to parse given string' in ini_bad_file_debug.msg" + - "'File contains no section headers' in ini_bad_file_debug.msg" +... diff --git a/ansible_collections/community/general/tests/integration/targets/filter_from_ini/vars/main.yml b/ansible_collections/community/general/tests/integration/targets/filter_from_ini/vars/main.yml new file mode 100644 index 000000000..8c4d79327 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/filter_from_ini/vars/main.yml @@ -0,0 +1,8 @@ +--- +# Copyright (c) 2023, Steffen Scheib <steffen@scheib.me> +# 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 + +ini_test_file: '/tmp/test.ini' +ini_bad_file: '/tmp/bad.file' +... diff --git a/ansible_collections/community/general/tests/integration/targets/filter_groupby_as_dict/aliases b/ansible_collections/community/general/tests/integration/targets/filter_groupby_as_dict/aliases index e8051e042..343f119da 100644 --- a/ansible_collections/community/general/tests/integration/targets/filter_groupby_as_dict/aliases +++ b/ansible_collections/community/general/tests/integration/targets/filter_groupby_as_dict/aliases @@ -3,4 +3,3 @@ # SPDX-License-Identifier: GPL-3.0-or-later azp/posix/3 -skip/python2.6 # filters are controller only, and we no longer support Python 2.6 on the controller diff --git a/ansible_collections/community/general/tests/integration/targets/filter_hashids/aliases b/ansible_collections/community/general/tests/integration/targets/filter_hashids/aliases index bc9b4bc99..12d1d6617 100644 --- a/ansible_collections/community/general/tests/integration/targets/filter_hashids/aliases +++ b/ansible_collections/community/general/tests/integration/targets/filter_hashids/aliases @@ -3,4 +3,3 @@ # SPDX-License-Identifier: GPL-3.0-or-later azp/posix/2 -skip/python2.6 # filters are controller only, and we no longer support Python 2.6 on the controller diff --git a/ansible_collections/community/general/tests/integration/targets/filter_jc/aliases b/ansible_collections/community/general/tests/integration/targets/filter_jc/aliases index 0e799090e..4e1151566 100644 --- a/ansible_collections/community/general/tests/integration/targets/filter_jc/aliases +++ b/ansible_collections/community/general/tests/integration/targets/filter_jc/aliases @@ -3,5 +3,6 @@ # SPDX-License-Identifier: GPL-3.0-or-later azp/posix/2 -skip/python2.6 # filters are controller only, and we no longer support Python 2.6 on the controller skip/python2.7 # jc only supports python3.x +skip/freebsd13.3 # FIXME - ruyaml compilation fails +skip/freebsd14.0 # FIXME - ruyaml compilation fails diff --git a/ansible_collections/community/general/tests/integration/targets/filter_json_query/aliases b/ansible_collections/community/general/tests/integration/targets/filter_json_query/aliases index cee9abd2c..dadd9f37a 100644 --- a/ansible_collections/community/general/tests/integration/targets/filter_json_query/aliases +++ b/ansible_collections/community/general/tests/integration/targets/filter_json_query/aliases @@ -3,5 +3,4 @@ # SPDX-License-Identifier: GPL-3.0-or-later azp/posix/2 -skip/python2.6 # filters are controller only, and we no longer support Python 2.6 on the controller skip/aix diff --git a/ansible_collections/community/general/tests/integration/targets/filter_lists/aliases b/ansible_collections/community/general/tests/integration/targets/filter_lists/aliases new file mode 100644 index 000000000..12d1d6617 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/filter_lists/aliases @@ -0,0 +1,5 @@ +# 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 diff --git a/ansible_collections/community/general/tests/integration/targets/filter_lists/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/filter_lists/tasks/main.yml new file mode 100644 index 000000000..a146e1293 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/filter_lists/tasks/main.yml @@ -0,0 +1,64 @@ +--- +# 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: Test predictive list union + ansible.builtin.assert: + that: + - 'list1 | community.general.lists_union(list2, list3) == [1, 2, 5, 3, 4, 10, 11, 99, 101]' + - '[list1, list2, list3] | community.general.lists_union(flatten=True) == [1, 2, 5, 3, 4, 10, 11, 99, 101]' + - '[1, 2, 3] | community.general.lists_union([4, 5, 6]) == [1, 2, 3, 4, 5, 6]' + - '[1, 2, 3] | community.general.lists_union([3, 4, 5, 6]) == [1, 2, 3, 4, 5, 6]' + - '[1, 2, 3] | community.general.lists_union([3, 2, 1]) == [1, 2, 3]' + - '["a", "A", "b"] | community.general.lists_union(["B", "c", "C"]) == ["a", "A", "b", "B", "c", "C"]' + - '["a", "A", "b"] | community.general.lists_union(["b", "B", "c", "C"]) == ["a", "A", "b", "B", "c", "C"]' + - '["a", "A", "b"] | community.general.lists_union(["b", "A", "a"]) == ["a", "A", "b"]' + - '[["a"]] | community.general.lists_union([["b"], ["a"]]) == [["a"], ["b"]]' + - '[["a"]] | community.general.lists_union([["b"]], ["a"]) == [["a"], ["b"], "a"]' + - '[["a"]] | community.general.lists_union(["b"], ["a"]) == [["a"], "b", "a"]' + +- name: Test predictive list intersection + ansible.builtin.assert: + that: + - 'list1 | community.general.lists_intersect(list2, list3) == [1, 2, 5, 4]' + - '[list1, list2, list3] | community.general.lists_intersect(flatten=True) == [1, 2, 5, 4]' + - '[1, 2, 3] | community.general.lists_intersect([4, 5, 6]) == []' + - '[1, 2, 3] | community.general.lists_intersect([3, 4, 5, 6]) == [3]' + - '[1, 2, 3] | community.general.lists_intersect([3, 2, 1]) == [1, 2, 3]' + - '["a", "A", "b"] | community.general.lists_intersect(["B", "c", "C"]) == []' + - '["a", "A", "b"] | community.general.lists_intersect(["b", "B", "c", "C"]) == ["b"]' + - '["a", "A", "b"] | community.general.lists_intersect(["b", "A", "a"]) == ["a", "A", "b"]' + - '[["a"]] | community.general.lists_intersect([["b"], ["a"]]) == [["a"]]' + - '[["a"]] | community.general.lists_intersect([["b"]], ["a"]) == []' + - '[["a"]] | community.general.lists_intersect(["b"], ["a"]) == []' + +- name: Test predictive list difference + ansible.builtin.assert: + that: + - 'list1 | community.general.lists_difference(list2, list3) == []' + - '[list1, list2, list3] | community.general.lists_difference(flatten=True) == []' + - '[1, 2, 3] | community.general.lists_difference([4, 5, 6]) == [1, 2, 3]' + - '[1, 2, 3] | community.general.lists_difference([3, 4, 5, 6]) == [1, 2]' + - '[1, 2, 3] | community.general.lists_difference([3, 2, 1]) == []' + - '["a", "A", "b"] | community.general.lists_difference(["B", "c", "C"]) == ["a", "A", "b"]' + - '["a", "A", "b"] | community.general.lists_difference(["b", "B", "c", "C"]) == ["a", "A"]' + - '["a", "A", "b"] | community.general.lists_difference(["b", "A", "a"]) == []' + - '[["a"]] | community.general.lists_difference([["b"], ["a"]]) == []' + - '[["a"]] | community.general.lists_difference([["b"]], ["a"]) == [["a"]]' + - '[["a"]] | community.general.lists_difference(["b"], ["a"]) == [["a"]]' + +- name: Test predictive list symmetric difference + ansible.builtin.assert: + that: + - 'list1 | community.general.lists_symmetric_difference(list2, list3) == [11, 1, 2, 4, 5, 101]' + - '[list1, list2, list3] | community.general.lists_symmetric_difference(flatten=True) == [11, 1, 2, 4, 5, 101]' + - '[1, 2, 3] | community.general.lists_symmetric_difference([4, 5, 6]) == [1, 2, 3, 4, 5, 6]' + - '[1, 2, 3] | community.general.lists_symmetric_difference([3, 4, 5, 6]) == [1, 2, 4, 5, 6]' + - '[1, 2, 3] | community.general.lists_symmetric_difference([3, 2, 1]) == []' + - '["a", "A", "b"] | community.general.lists_symmetric_difference(["B", "c", "C"]) == ["a", "A", "b", "B", "c", "C"]' + - '["a", "A", "b"] | community.general.lists_symmetric_difference(["b", "B", "c", "C"]) == ["a", "A", "B", "c", "C"]' + - '["a", "A", "b"] | community.general.lists_symmetric_difference(["b", "A", "a"]) == []' + - '[["a"]] | community.general.lists_symmetric_difference([["b"], ["a"]]) == [["b"]]' + - '[["a"]] | community.general.lists_symmetric_difference([["b"]], ["a"]) == [["a"], ["b"], "a"]' + - '[["a"]] | community.general.lists_symmetric_difference(["b"], ["a"]) == [["a"], "b", "a"]' diff --git a/ansible_collections/community/general/tests/integration/targets/filter_lists/vars/main.yml b/ansible_collections/community/general/tests/integration/targets/filter_lists/vars/main.yml new file mode 100644 index 000000000..a67af1dad --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/filter_lists/vars/main.yml @@ -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 + +list1: [1, 2, 5, 3, 4, 10] +list2: [1, 2, 3, 4, 5, 11, 99] +list3: [1, 2, 4, 5, 10, 99, 101] diff --git a/ansible_collections/community/general/tests/integration/targets/filter_lists_mergeby/aliases b/ansible_collections/community/general/tests/integration/targets/filter_lists_mergeby/aliases index bc9b4bc99..12d1d6617 100644 --- a/ansible_collections/community/general/tests/integration/targets/filter_lists_mergeby/aliases +++ b/ansible_collections/community/general/tests/integration/targets/filter_lists_mergeby/aliases @@ -3,4 +3,3 @@ # SPDX-License-Identifier: GPL-3.0-or-later azp/posix/2 -skip/python2.6 # filters are controller only, and we no longer support Python 2.6 on the controller diff --git a/ansible_collections/community/general/tests/integration/targets/filter_path_join_shim/aliases b/ansible_collections/community/general/tests/integration/targets/filter_path_join_shim/aliases index 51baa3d7a..afda346c4 100644 --- a/ansible_collections/community/general/tests/integration/targets/filter_path_join_shim/aliases +++ b/ansible_collections/community/general/tests/integration/targets/filter_path_join_shim/aliases @@ -3,4 +3,3 @@ # SPDX-License-Identifier: GPL-3.0-or-later azp/posix/1 -skip/python2.6 # filters are controller only, and we no longer support Python 2.6 on the controller diff --git a/ansible_collections/community/general/tests/integration/targets/filter_random_mac/aliases b/ansible_collections/community/general/tests/integration/targets/filter_random_mac/aliases index cee9abd2c..dadd9f37a 100644 --- a/ansible_collections/community/general/tests/integration/targets/filter_random_mac/aliases +++ b/ansible_collections/community/general/tests/integration/targets/filter_random_mac/aliases @@ -3,5 +3,4 @@ # SPDX-License-Identifier: GPL-3.0-or-later azp/posix/2 -skip/python2.6 # filters are controller only, and we no longer support Python 2.6 on the controller skip/aix diff --git a/ansible_collections/community/general/tests/integration/targets/filter_time/aliases b/ansible_collections/community/general/tests/integration/targets/filter_time/aliases index bc9b4bc99..12d1d6617 100644 --- a/ansible_collections/community/general/tests/integration/targets/filter_time/aliases +++ b/ansible_collections/community/general/tests/integration/targets/filter_time/aliases @@ -3,4 +3,3 @@ # SPDX-License-Identifier: GPL-3.0-or-later azp/posix/2 -skip/python2.6 # filters are controller only, and we no longer support Python 2.6 on the controller diff --git a/ansible_collections/community/general/tests/integration/targets/filter_to_ini/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/filter_to_ini/tasks/main.yml new file mode 100644 index 000000000..877d4471d --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/filter_to_ini/tasks/main.yml @@ -0,0 +1,58 @@ +--- +# Copyright (c) 2023, Steffen Scheib <steffen@scheib.me> +# 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: >- + Write INI file that reflects using to_ini to {{ ini_test_file_filter }} + ansible.builtin.copy: + dest: '{{ ini_test_file_filter }}' + content: '{{ ini_test_dict | community.general.to_ini }}' + vars: + ini_test_dict: + section_name: + key_name: 'key value' + + another_section: + connection: 'ssh' + +- name: 'Write INI file manually to {{ ini_test_file }}' + ansible.builtin.copy: + dest: '{{ ini_test_file }}' + content: | + [section_name] + key_name = key value + + [another_section] + connection = ssh + +- name: 'Slurp the manually created test file: {{ ini_test_file }}' + ansible.builtin.slurp: + src: '{{ ini_test_file }}' + register: 'ini_file_content' + +- name: 'Slurp the test file created with to_ini: {{ ini_test_file_filter }}' + ansible.builtin.slurp: + src: '{{ ini_test_file_filter }}' + register: 'ini_file_filter_content' + +- name: >- + Ensure the manually created test file and the test file created with + to_ini are identical + ansible.builtin.assert: + that: + - 'ini_file_content.content | b64decode == + ini_file_filter_content.content | b64decode' + +- name: 'Try to convert an empty dictionary with to_ini' + ansible.builtin.debug: + msg: '{{ {} | community.general.to_ini }}' + register: 'ini_empty_dict' + ignore_errors: true + +- name: 'Ensure the correct exception was raised' + ansible.builtin.assert: + that: + - "'to_ini received an empty dict. An empty dict cannot be converted.' in + ini_empty_dict.msg" +... diff --git a/ansible_collections/community/general/tests/integration/targets/filter_to_ini/vars/main.yml b/ansible_collections/community/general/tests/integration/targets/filter_to_ini/vars/main.yml new file mode 100644 index 000000000..9c950726b --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/filter_to_ini/vars/main.yml @@ -0,0 +1,8 @@ +--- +# Copyright (c) 2023, Steffen Scheib <steffen@scheib.me> +# 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 + +ini_test_file: '/tmp/test.ini' +ini_test_file_filter: '/tmp/test_filter.ini' +... diff --git a/ansible_collections/community/general/tests/integration/targets/filter_unicode_normalize/aliases b/ansible_collections/community/general/tests/integration/targets/filter_unicode_normalize/aliases index bc9b4bc99..12d1d6617 100644 --- a/ansible_collections/community/general/tests/integration/targets/filter_unicode_normalize/aliases +++ b/ansible_collections/community/general/tests/integration/targets/filter_unicode_normalize/aliases @@ -3,4 +3,3 @@ # SPDX-License-Identifier: GPL-3.0-or-later azp/posix/2 -skip/python2.6 # filters are controller only, and we no longer support Python 2.6 on the controller diff --git a/ansible_collections/community/general/tests/integration/targets/filter_version_sort/aliases b/ansible_collections/community/general/tests/integration/targets/filter_version_sort/aliases index bc9b4bc99..12d1d6617 100644 --- a/ansible_collections/community/general/tests/integration/targets/filter_version_sort/aliases +++ b/ansible_collections/community/general/tests/integration/targets/filter_version_sort/aliases @@ -3,4 +3,3 @@ # SPDX-License-Identifier: GPL-3.0-or-later azp/posix/2 -skip/python2.6 # filters are controller only, and we no longer support Python 2.6 on the controller diff --git a/ansible_collections/community/general/tests/integration/targets/gem/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/gem/tasks/main.yml index 362c126bf..2d615304f 100644 --- a/ansible_collections/community/general/tests/integration/targets/gem/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/gem/tasks/main.yml @@ -109,7 +109,7 @@ - current_gems.stdout is not search('gist\s+\([0-9.]+\)') when: ansible_user_uid == 0 - # Check cutom gem directory + # Check custom gem directory - name: Install gem in a custom directory with incorrect options gem: name: gist diff --git a/ansible_collections/community/general/tests/integration/targets/git_config/files/gitconfig b/ansible_collections/community/general/tests/integration/targets/git_config/files/gitconfig index 92eeb7eb9..29d3e8a0e 100644 --- a/ansible_collections/community/general/tests/integration/targets/git_config/files/gitconfig +++ b/ansible_collections/community/general/tests/integration/targets/git_config/files/gitconfig @@ -4,3 +4,8 @@ [http] proxy = foo + +[push] +pushoption = merge_request.create +pushoption = merge_request.draft +pushoption = merge_request.target=foobar diff --git a/ansible_collections/community/general/tests/integration/targets/git_config/tasks/get_set_state_present_file.yml b/ansible_collections/community/general/tests/integration/targets/git_config/tasks/get_set_state_present_file.yml index a61ffcc68..c410bfe18 100644 --- a/ansible_collections/community/general/tests/integration/targets/git_config/tasks/get_set_state_present_file.yml +++ b/ansible_collections/community/general/tests/integration/targets/git_config/tasks/get_set_state_present_file.yml @@ -30,3 +30,4 @@ - set_result.diff.after == option_value + "\n" - get_result is not changed - get_result.config_value == option_value +...
\ No newline at end of file diff --git a/ansible_collections/community/general/tests/integration/targets/git_config/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/git_config/tasks/main.yml index 4dc72824c..5fddaf764 100644 --- a/ansible_collections/community/general/tests/integration/targets/git_config/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/git_config/tasks/main.yml @@ -13,6 +13,7 @@ import_tasks: setup.yml - block: + - import_tasks: set_value.yml # testing parameters exclusion: state and list_all - import_tasks: exclusion_state_list-all.yml # testing get/set option without state @@ -31,5 +32,7 @@ - import_tasks: unset_check_mode.yml # testing for case in issue #1776 - import_tasks: set_value_with_tilde.yml + - import_tasks: set_multi_value.yml + - import_tasks: unset_multi_value.yml when: git_installed is succeeded and git_version.stdout is version(git_version_supporting_includes, ">=") ... diff --git a/ansible_collections/community/general/tests/integration/targets/git_config/tasks/set_multi_value.yml b/ansible_collections/community/general/tests/integration/targets/git_config/tasks/set_multi_value.yml new file mode 100644 index 000000000..8d2710b76 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/git_config/tasks/set_multi_value.yml @@ -0,0 +1,79 @@ +--- +# 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 + +- import_tasks: setup_no_value.yml + +- name: setting value + git_config: + name: push.pushoption + add_mode: add + value: "{{ item }}" + state: present + scope: global + loop: + - 'merge_request.create' + - 'merge_request.draft' + - 'merge_request.target=foobar' + register: set_result1 + +- name: setting value + git_config: + name: push.pushoption + add_mode: add + value: "{{ item }}" + state: present + scope: global + loop: + - 'merge_request.create' + - 'merge_request.draft' + - 'merge_request.target=foobar' + register: set_result2 + +- name: getting the multi-value + git_config: + name: push.pushoption + scope: global + register: get_single_result + +- name: getting all values for the single option + git_config_info: + name: push.pushoption + scope: global + register: get_all_result + +- name: replace-all values + git_config: + name: push.pushoption + add_mode: replace-all + value: merge_request.create + state: present + scope: global + register: set_result3 + +- name: assert set changed and value is correct + assert: + that: + - set_result1.results[0] is changed + - set_result1.results[1] is changed + - set_result1.results[2] is changed + - set_result2.results[0] is not changed + - set_result2.results[1] is not changed + - set_result2.results[2] is not changed + - set_result3 is changed + - get_single_result.config_value == 'merge_request.create' + - 'get_all_result.config_values == {"push.pushoption": ["merge_request.create", "merge_request.draft", "merge_request.target=foobar"]}' + +- name: assert the diffs are also right + assert: + that: + - set_result1.results[0].diff.before == "\n" + - set_result1.results[0].diff.after == "merge_request.create\n" + - set_result1.results[1].diff.before == "merge_request.create\n" + - set_result1.results[1].diff.after == ["merge_request.create", "merge_request.draft"] + - set_result1.results[2].diff.before == ["merge_request.create", "merge_request.draft"] + - set_result1.results[2].diff.after == ["merge_request.create", "merge_request.draft", "merge_request.target=foobar"] + - set_result3.diff.before == ["merge_request.create", "merge_request.draft", "merge_request.target=foobar"] + - set_result3.diff.after == "merge_request.create\n" +... diff --git a/ansible_collections/community/general/tests/integration/targets/git_config/tasks/set_value.yml b/ansible_collections/community/general/tests/integration/targets/git_config/tasks/set_value.yml new file mode 100644 index 000000000..774e3136a --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/git_config/tasks/set_value.yml @@ -0,0 +1,39 @@ +--- +# 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 + +- import_tasks: setup_no_value.yml + +- name: setting value + git_config: + name: core.name + value: foo + scope: global + register: set_result1 + +- name: setting another value for same name + git_config: + name: core.name + value: bar + scope: global + register: set_result2 + +- name: getting value + git_config: + name: core.name + scope: global + register: get_result + +- name: assert set changed and value is correct + assert: + that: + - set_result1 is changed + - set_result2 is changed + - get_result is not changed + - get_result.config_value == 'bar' + - set_result1.diff.before == "\n" + - set_result1.diff.after == "foo\n" + - set_result2.diff.before == "foo\n" + - set_result2.diff.after == "bar\n" +... diff --git a/ansible_collections/community/general/tests/integration/targets/git_config/tasks/set_value_with_tilde.yml b/ansible_collections/community/general/tests/integration/targets/git_config/tasks/set_value_with_tilde.yml index f78e709bd..3ca9023aa 100644 --- a/ansible_collections/community/general/tests/integration/targets/git_config/tasks/set_value_with_tilde.yml +++ b/ansible_collections/community/general/tests/integration/targets/git_config/tasks/set_value_with_tilde.yml @@ -11,7 +11,7 @@ value: '~/foo/bar' state: present scope: global - register: set_result + register: set_result1 - name: setting value again git_config: @@ -30,7 +30,7 @@ - name: assert set changed and value is correct assert: that: - - set_result is changed + - set_result1 is changed - set_result2 is not changed - get_result is not changed - get_result.config_value == '~/foo/bar' diff --git a/ansible_collections/community/general/tests/integration/targets/git_config/tasks/unset_multi_value.yml b/ansible_collections/community/general/tests/integration/targets/git_config/tasks/unset_multi_value.yml new file mode 100644 index 000000000..4cb9dee49 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/git_config/tasks/unset_multi_value.yml @@ -0,0 +1,28 @@ +--- +# 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 + +- import_tasks: setup_value.yml + +- name: unsetting "push.pushoption" + git_config: + name: push.pushoption + scope: global + state: absent + register: unset_result + +- name: getting all pushoptions values + git_config_info: + name: push.pushoption + scope: global + register: get_all_result + +- name: assert unsetting muti-values + assert: + that: + - unset_result is changed + - 'get_all_result.config_values == {"push.pushoption": []}' + - unset_result.diff.before == ["merge_request.create", "merge_request.draft", "merge_request.target=foobar"] + - unset_result.diff.after == "\n" +... diff --git a/ansible_collections/community/general/tests/integration/targets/git_config_info/aliases b/ansible_collections/community/general/tests/integration/targets/git_config_info/aliases new file mode 100644 index 000000000..7b8c653de --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/git_config_info/aliases @@ -0,0 +1,7 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/posix/3 +skip/aix +destructive diff --git a/ansible_collections/community/general/tests/integration/targets/git_config_info/files/gitconfig b/ansible_collections/community/general/tests/integration/targets/git_config_info/files/gitconfig new file mode 100644 index 000000000..d0590b3f8 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/git_config_info/files/gitconfig @@ -0,0 +1,11 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +[credential "https://some.com"] + username = yolo +[user] + name = foobar +[push] + pushoption = merge_request.create + pushoption = merge_request.draft diff --git a/ansible_collections/community/general/tests/integration/targets/git_config_info/meta/main.yml b/ansible_collections/community/general/tests/integration/targets/git_config_info/meta/main.yml new file mode 100644 index 000000000..982de6eb0 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/git_config_info/meta/main.yml @@ -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 diff --git a/ansible_collections/community/general/tests/integration/targets/git_config_info/tasks/error_handling.yml b/ansible_collections/community/general/tests/integration/targets/git_config_info/tasks/error_handling.yml new file mode 100644 index 000000000..1b84fee50 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/git_config_info/tasks/error_handling.yml @@ -0,0 +1,26 @@ +--- +# 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 + +- import_tasks: "setup_file.yml" + +- name: getting all system configs + git_config_info: + scope: system + register: get_result1 + +- name: getting all system configs for a key + git_config_info: + name: user.name + scope: system + register: get_result2 + +- name: assert value is correct + assert: + that: + - get_result1.config_value == "" + - 'get_result1.config_values == {}' + - get_result2.config_value == "" + - 'get_result2.config_values == {"user.name": []}' +... diff --git a/ansible_collections/community/general/tests/integration/targets/git_config_info/tasks/get_all_values.yml b/ansible_collections/community/general/tests/integration/targets/git_config_info/tasks/get_all_values.yml new file mode 100644 index 000000000..301051a42 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/git_config_info/tasks/get_all_values.yml @@ -0,0 +1,19 @@ +--- +# 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 + +- include_tasks: "{{ item.import_file }}" + +- name: getting all values (as list) for a file config + git_config_info: + scope: "{{ item.git_scope }}" + path: "{{ item.git_file | default(omit) }}" + register: get_result + +- name: assert value is correct + assert: + that: + - get_result.config_value == "" + - 'get_result.config_values == {"credential.https://some.com.username": ["yolo"], "user.name": ["foobar"], "push.pushoption": ["merge_request.create", "merge_request.draft"]}' +... diff --git a/ansible_collections/community/general/tests/integration/targets/git_config_info/tasks/get_multi_value.yml b/ansible_collections/community/general/tests/integration/targets/git_config_info/tasks/get_multi_value.yml new file mode 100644 index 000000000..14fa2800c --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/git_config_info/tasks/get_multi_value.yml @@ -0,0 +1,20 @@ +--- +# 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 + +- include_tasks: "{{ item.import_file }}" + +- name: getting only a single value (as string) from an option with multiple values in the git config file + git_config_info: + name: push.pushoption + scope: "{{ item.git_scope }}" + path: "{{ item.git_file | default(omit) }}" + register: get_result + +- name: assert value is correct + assert: + that: + - get_result.config_value == "merge_request.create" + - 'get_result.config_values == {"push.pushoption": ["merge_request.create", "merge_request.draft"]}' +... diff --git a/ansible_collections/community/general/tests/integration/targets/git_config_info/tasks/get_simple_value.yml b/ansible_collections/community/general/tests/integration/targets/git_config_info/tasks/get_simple_value.yml new file mode 100644 index 000000000..83cd19a0b --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/git_config_info/tasks/get_simple_value.yml @@ -0,0 +1,38 @@ +--- +# 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 + +- include_tasks: "{{ item.import_file }}" + +- name: getting simple file value1 + git_config_info: + name: user.name + scope: "{{ item.git_scope }}" + path: "{{ item.git_file | default(omit) }}" + register: get_result1 + +- name: getting simple file value2 + git_config_info: + name: "credential.https://some.com.username" + scope: "{{ item.git_scope }}" + path: "{{ item.git_file | default(omit) }}" + register: get_result2 + +- name: getting not existing value + git_config_info: + name: "user.email" + scope: "{{ item.git_scope }}" + path: "{{ item.git_file | default(omit) }}" + register: get_result3 + +- name: assert value is correct + assert: + that: + - get_result1.config_value == "foobar" + - 'get_result1.config_values == {"user.name": ["foobar"]}' + - get_result2.config_value == "yolo" + - 'get_result2.config_values == {"credential.https://some.com.username": ["yolo"]}' + - get_result3.config_value == "" + - 'get_result3.config_values == {"user.email": []}' +... diff --git a/ansible_collections/community/general/tests/integration/targets/git_config_info/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/git_config_info/tasks/main.yml new file mode 100644 index 000000000..993238805 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/git_config_info/tasks/main.yml @@ -0,0 +1,33 @@ +--- +#################################################################### +# 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 git_config_info module +# 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: setup + import_tasks: setup.yml + +- block: + - include_tasks: get_simple_value.yml + loop: + - { import_file: setup_global.yml, git_scope: 'global' } + - { import_file: setup_file.yml, git_scope: 'file', git_file: "{{ remote_tmp_dir }}/gitconfig_file" } + + - include_tasks: get_multi_value.yml + loop: + - { import_file: setup_global.yml, git_scope: 'global' } + - { import_file: setup_file.yml, git_scope: 'file', git_file: "{{ remote_tmp_dir }}/gitconfig_file" } + + - include_tasks: get_all_values.yml + loop: + - { import_file: setup_global.yml, git_scope: 'global' } + - { import_file: setup_file.yml, git_scope: 'file', git_file: "{{ remote_tmp_dir }}/gitconfig_file" } + + - include_tasks: error_handling.yml + when: git_installed is succeeded and git_version.stdout is version(git_version_supporting_includes, ">=") +... diff --git a/ansible_collections/community/general/tests/integration/targets/git_config_info/tasks/setup.yml b/ansible_collections/community/general/tests/integration/targets/git_config_info/tasks/setup.yml new file mode 100644 index 000000000..6e5516da5 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/git_config_info/tasks/setup.yml @@ -0,0 +1,15 @@ +--- +# 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: verify that git is installed so this test can continue + command: which git + register: git_installed + ignore_errors: true + +- name: get git version, only newer than {{git_version_supporting_includes}} has includes option + shell: "git --version | grep 'git version' | sed 's/git version //'" + register: git_version + ignore_errors: true +... diff --git a/ansible_collections/community/general/tests/integration/targets/git_config_info/tasks/setup_file.yml b/ansible_collections/community/general/tests/integration/targets/git_config_info/tasks/setup_file.yml new file mode 100644 index 000000000..854b10997 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/git_config_info/tasks/setup_file.yml @@ -0,0 +1,16 @@ +--- +# 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 + +# ------ +# set up : set gitconfig with value +- name: delete global config + file: + path: ~/.gitconfig + state: absent + +- name: set up file config + copy: + src: gitconfig + dest: "{{ remote_tmp_dir }}/gitconfig_file" diff --git a/ansible_collections/community/general/tests/integration/targets/git_config_info/tasks/setup_global.yml b/ansible_collections/community/general/tests/integration/targets/git_config_info/tasks/setup_global.yml new file mode 100644 index 000000000..a9e045a57 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/git_config_info/tasks/setup_global.yml @@ -0,0 +1,16 @@ +--- +# 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 + +# ------ +# set up : set gitconfig with value +- name: delete file config + file: + path: "{{ remote_tmp_dir }}/gitconfig_file" + state: absent + +- name: setup global config + copy: + src: gitconfig + dest: ~/.gitconfig diff --git a/ansible_collections/community/general/tests/integration/targets/git_config_info/vars/main.yml b/ansible_collections/community/general/tests/integration/targets/git_config_info/vars/main.yml new file mode 100644 index 000000000..55c3d1738 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/git_config_info/vars/main.yml @@ -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 + +git_version_supporting_includes: 1.7.10 +... diff --git a/ansible_collections/community/general/tests/integration/targets/gitlab_group_access_token/aliases b/ansible_collections/community/general/tests/integration/targets/gitlab_group_access_token/aliases new file mode 100644 index 000000000..fc0e157c9 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/gitlab_group_access_token/aliases @@ -0,0 +1,7 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/posix/1 +gitlab/ci +disabled diff --git a/ansible_collections/community/general/tests/integration/targets/gitlab_group_access_token/defaults/main.yml b/ansible_collections/community/general/tests/integration/targets/gitlab_group_access_token/defaults/main.yml new file mode 100644 index 000000000..1b0dab289 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/gitlab_group_access_token/defaults/main.yml @@ -0,0 +1,15 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Copyright (c) 2024, Zoran Krleza <zoran.krleza@true-north.hr> +# 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 + +gitlab_api_token: +gitlab_api_url: +gitlab_validate_certs: false +gitlab_group_name: +gitlab_token_name: diff --git a/ansible_collections/community/general/tests/integration/targets/gitlab_group_access_token/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/gitlab_group_access_token/tasks/main.yml new file mode 100644 index 000000000..4e6234238 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/gitlab_group_access_token/tasks/main.yml @@ -0,0 +1,221 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Copyright (c) 2024, Zoran Krleza <zoran.krleza@true-north.hr> +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install required libs + pip: + name: python-gitlab + state: present + +- block: + - name: Try to create access token in nonexisting group + community.general.gitlab_group_access_token: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_api_url }}" + validate_certs: "{{ gitlab_validate_certs }}" + group: "some_nonexisting_group" + name: "{{ gitlab_token_name }}" + state: present + expires_at: '2025-01-01' + access_level: developer + scopes: + - api + - read_api + register: create_pfail_token_status + always: + - name: Assert that token creation in nonexisting group failed + assert: + that: + - create_pfail_token_status is failed + ignore_errors: true + +- block: + - name: Try to create access token with nonvalid expires_at + community.general.gitlab_group_access_token: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_api_url }}" + validate_certs: "{{ gitlab_validate_certs }}" + group: "some_nonexisting_group" + name: "{{ gitlab_token_name }}" + state: present + expires_at: '2025-13-01' + access_level: developer + scopes: + - api + - read_api + register: create_efail_token_status + always: + - name: Assert that token creation with invalid expires_at failed + assert: + that: + - create_efail_token_status is failed + ignore_errors: true + +- name: Create access token + community.general.gitlab_group_access_token: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_api_url }}" + validate_certs: "{{ gitlab_validate_certs }}" + group: "{{ gitlab_group_name }}" + name: "{{ gitlab_token_name }}" + state: present + expires_at: '2024-12-31' + access_level: developer + scopes: + - api + - read_api + register: create_token_status +- name: Assert that token creation with valid arguments is successfull + assert: + that: + - create_token_status is changed + - create_token_status.access_token.token is defined + +- name: Check existing access token recreate=never (default) + community.general.gitlab_group_access_token: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_api_url }}" + validate_certs: "{{ gitlab_validate_certs }}" + group: "{{ gitlab_group_name }}" + name: "{{ gitlab_token_name }}" + state: present + expires_at: '2024-12-31' + access_level: developer + scopes: + - api + - read_api + register: check_token_status +- name: Assert that token creation without changes and recreate=never succeeds with status not changed + assert: + that: + - check_token_status is not changed + - check_token_status.access_token.token is not defined + +- name: Check existing access token with recreate=state_change + community.general.gitlab_group_access_token: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_api_url }}" + validate_certs: "{{ gitlab_validate_certs }}" + group: "{{ gitlab_group_name }}" + name: "{{ gitlab_token_name }}" + state: present + expires_at: '2024-12-31' + access_level: developer + scopes: + - api + - read_api + recreate: state_change + register: check_recreate_token_status +- name: Assert that token creation without changes and recreate=state_change succeeds with status not changed + assert: + that: + - check_recreate_token_status is not changed + - check_recreate_token_status.access_token.token is not defined + +- block: + - name: Try to change existing access token with recreate=never + community.general.gitlab_group_access_token: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_api_url }}" + validate_certs: "{{ gitlab_validate_certs }}" + group: "{{ gitlab_group_name }}" + name: "{{ gitlab_token_name }}" + state: present + expires_at: '2025-01-01' + access_level: developer + scopes: + - api + - read_api + register: change_token_status + always: + - name: Assert that token change with recreate=never fails + assert: + that: + - change_token_status is failed + ignore_errors: true + +- name: Try to change existing access token with recreate=state_change + community.general.gitlab_group_access_token: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_api_url }}" + validate_certs: "{{ gitlab_validate_certs }}" + group: "{{ gitlab_group_name }}" + name: "{{ gitlab_token_name }}" + state: present + expires_at: '2025-01-01' + access_level: developer + scopes: + - api + - read_api + recreate: state_change + register: change_recreate_token_status +- name: Assert that token change with recreate=state_change succeeds + assert: + that: + - change_recreate_token_status is changed + - change_recreate_token_status.access_token.token is defined + +- name: Try to change existing access token with recreate=always + community.general.gitlab_group_access_token: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_api_url }}" + validate_certs: "{{ gitlab_validate_certs }}" + group: "{{ gitlab_group_name }}" + name: "{{ gitlab_token_name }}" + state: present + expires_at: '2025-01-01' + access_level: developer + scopes: + - api + - read_api + recreate: always + register: change_recreate1_token_status +- name: Assert that token change with recreate=always succeeds + assert: + that: + - change_recreate1_token_status is changed + - change_recreate1_token_status.access_token.token is defined + +- name: Revoke access token + community.general.gitlab_group_access_token: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_api_url }}" + validate_certs: "{{ gitlab_validate_certs }}" + group: "{{ gitlab_group_name }}" + name: "{{ gitlab_token_name }}" + state: absent + expires_at: '2024-12-31' + access_level: developer + scopes: + - api + - read_api + register: revoke_token_status +- name: Assert that token revocation succeeds + assert: + that: + - revoke_token_status is changed + +- name: Revoke nonexisting access token + community.general.gitlab_group_access_token: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_api_url }}" + validate_certs: "{{ gitlab_validate_certs }}" + group: "{{ gitlab_group_name }}" + name: "{{ gitlab_token_name }}" + state: absent + expires_at: '2024-12-31' + access_level: developer + scopes: + - api + - read_api + register: revoke_token_status +- name: Assert that token revocation succeeds with status not changed + assert: + that: + - revoke_token_status is not changed
\ No newline at end of file diff --git a/ansible_collections/community/general/tests/integration/targets/gitlab_instance_variable/aliases b/ansible_collections/community/general/tests/integration/targets/gitlab_instance_variable/aliases new file mode 100644 index 000000000..bd1f02444 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/gitlab_instance_variable/aliases @@ -0,0 +1,5 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +unsupported diff --git a/ansible_collections/community/general/tests/integration/targets/gitlab_instance_variable/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/gitlab_instance_variable/tasks/main.yml new file mode 100644 index 000000000..94a81698b --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/gitlab_instance_variable/tasks/main.yml @@ -0,0 +1,606 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install required libs + pip: + name: python-gitlab + state: present + +- name: purge all variables for check_mode test + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + purge: true + +- name: add a variable value in check_mode + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + variables: + - name: ACCESS_KEY_ID + value: checkmode + check_mode: true + register: gitlab_instance_variable_state + +- name: check_mode state must be changed + assert: + that: + - gitlab_instance_variable_state is changed + +- name: apply add value from check_mode test + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + variables: + - name: ACCESS_KEY_ID + value: checkmode + register: gitlab_instance_variable_state + +- name: state must be changed + assert: + that: + - gitlab_instance_variable_state is changed + +- name: apply same value again again + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + variables: + - name: ACCESS_KEY_ID + value: checkmode + register: gitlab_instance_variable_state + +- name: state must be not changed + assert: + that: + - gitlab_instance_variable_state is not changed + +- name: change protected attribute + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + variables: + - name: ACCESS_KEY_ID + value: checkmode + protected: true + register: gitlab_instance_variable_state + +- name: state must be changed + assert: + that: + - gitlab_instance_variable_state is changed + +- name: revert protected attribute + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + variables: + - name: ACCESS_KEY_ID + value: checkmode + protected: false + register: gitlab_instance_variable_state + +- name: state must be changed + assert: + that: + - gitlab_instance_variable_state is changed + +- name: change masked attribute + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + variables: + - name: ACCESS_KEY_ID + value: checkmode + masked: true + register: gitlab_instance_variable_state + +- name: state must be changed + assert: + that: + - gitlab_instance_variable_state is changed + +- name: revert masked attribute by not mention it + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + variables: + - name: ACCESS_KEY_ID + value: checkmode + masked: false + register: gitlab_instance_variable_state + +- name: state must be changed + assert: + that: + - gitlab_instance_variable_state is changed + +- name: revert again masked attribute by not mention it (idempotent) + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + variables: + - name: ACCESS_KEY_ID + value: checkmode + register: gitlab_instance_variable_state + +- name: state must be not changed + assert: + that: + - gitlab_instance_variable_state is not changed + +- name: set all attributes + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + variables: + - name: ACCESS_KEY_ID + value: checkmode + masked: true + protected: true + variable_type: env_var + register: gitlab_instance_variable_state + +- name: state must be changed + assert: + that: + - gitlab_instance_variable_state is changed + +- name: set again all attributes (idempotent) + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + variables: + - name: ACCESS_KEY_ID + value: checkmode + masked: true + protected: true + variable_type: env_var + register: gitlab_instance_variable_state + +- name: state must not be changed + assert: + that: + - gitlab_instance_variable_state is not changed + +- name: revert both (masked and protected) attribute + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + variables: + - name: ACCESS_KEY_ID + value: checkmode + protected: false + register: gitlab_instance_variable_state + +- name: state must be changed + assert: + that: + - gitlab_instance_variable_state is changed + +- name: change a variable value in check_mode again + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + variables: + - name: ACCESS_KEY_ID + value: checkmode + check_mode: true + register: gitlab_instance_variable_state + +- name: check_mode state must not be changed + assert: + that: + - gitlab_instance_variable_state is not changed + +- name: apply again the value change from check_mode test + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + variables: + - name: ACCESS_KEY_ID + value: checkmode + register: gitlab_instance_variable_state + +- name: state must not be changed + assert: + that: + - gitlab_instance_variable_state is not changed + +- name: purge all variables again + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + purge: true + +- name: set two test variables + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + variables: + - name: ACCESS_KEY_ID + value: abc123 + - name: SECRET_ACCESS_KEY + value: 321cba + register: gitlab_instance_variable_state + +- name: set two test variables state must be changed + assert: + that: + - gitlab_instance_variable_state is changed + - gitlab_instance_variable_state.instance_variable.added|length == 2 + - gitlab_instance_variable_state.instance_variable.untouched|length == 0 + - gitlab_instance_variable_state.instance_variable.removed|length == 0 + - gitlab_instance_variable_state.instance_variable.updated|length == 0 + +- name: re-set two test variables + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + variables: + - name: ACCESS_KEY_ID + value: abc123 + - name: SECRET_ACCESS_KEY + value: 321cba + register: gitlab_instance_variable_state + +- name: re-set two test variables state must not be changed + assert: + that: + - gitlab_instance_variable_state is not changed + - gitlab_instance_variable_state.instance_variable.added|length == 0 + - gitlab_instance_variable_state.instance_variable.untouched|length == 2 + - gitlab_instance_variable_state.instance_variable.removed|length == 0 + - gitlab_instance_variable_state.instance_variable.updated|length == 0 + +- name: edit one variable + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + variables: + - name: ACCESS_KEY_ID + value: changed + purge: false + register: gitlab_instance_variable_state + +- name: edit one variable state must be changed + assert: + that: + - gitlab_instance_variable_state.changed + - gitlab_instance_variable_state.instance_variable.added|length == 0 + - gitlab_instance_variable_state.instance_variable.untouched|length == 1 + - gitlab_instance_variable_state.instance_variable.removed|length == 0 + - gitlab_instance_variable_state.instance_variable.updated|length == 1 + - gitlab_instance_variable_state.instance_variable.updated[0] == "ACCESS_KEY_ID" + +- name: append one variable + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + variables: + - name: some + value: value + purge: false + register: gitlab_instance_variable_state + +- name: append one variable state must be changed + assert: + that: + - gitlab_instance_variable_state.changed + - gitlab_instance_variable_state.instance_variable.added|length == 1 + - gitlab_instance_variable_state.instance_variable.untouched|length == 2 + - gitlab_instance_variable_state.instance_variable.removed|length == 0 + - gitlab_instance_variable_state.instance_variable.updated|length == 0 + - gitlab_instance_variable_state.instance_variable.added[0] == "some" + +- name: re-set all variables + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + variables: + - name: ACCESS_KEY_ID + value: changed + - name: SECRET_ACCESS_KEY + value: 321cba + - name: some + value: value + register: gitlab_instance_variable_state + +- name: re-set all variables state must not be changed + assert: + that: + - not gitlab_instance_variable_state.changed + - gitlab_instance_variable_state.instance_variable.added|length == 0 + - gitlab_instance_variable_state.instance_variable.untouched|length == 3 + - gitlab_instance_variable_state.instance_variable.removed|length == 0 + - gitlab_instance_variable_state.instance_variable.updated|length == 0 + +- name: set one variables and purge all others + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + variables: + - name: some + value: value + purge: true + register: gitlab_instance_variable_state + +- name: set one variables and purge all others state must be changed + assert: + that: + - gitlab_instance_variable_state.changed + - gitlab_instance_variable_state.instance_variable.added|length == 0 + - gitlab_instance_variable_state.instance_variable.untouched|length == 1 + - gitlab_instance_variable_state.instance_variable.removed|length == 2 + - gitlab_instance_variable_state.instance_variable.updated|length == 0 + +- name: only one variable is left + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + variables: + - name: some + value: value + purge: false + register: gitlab_instance_variable_state + +- name: only one variable is left state must not be changed + assert: + that: + - not gitlab_instance_variable_state.changed + - gitlab_instance_variable_state.instance_variable.added|length == 0 + - gitlab_instance_variable_state.instance_variable.untouched|length == 1 + - gitlab_instance_variable_state.instance_variable.removed|length == 0 + - gitlab_instance_variable_state.instance_variable.updated|length == 0 + - gitlab_instance_variable_state.instance_variable.untouched[0] == "some" + +- name: test integer values + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + variables: + - name: some + value: 42 + purge: false + register: gitlab_instance_variable_state + +- name: only one variable is left state must be changed + assert: + that: + - gitlab_instance_variable_state.changed + - gitlab_instance_variable_state.instance_variable.added|length == 0 + - gitlab_instance_variable_state.instance_variable.untouched|length == 0 + - gitlab_instance_variable_state.instance_variable.removed|length == 0 + - gitlab_instance_variable_state.instance_variable.updated|length == 1 + +- name: test float values + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + variables: + - name: some + value: 42.23 + purge: false + register: gitlab_instance_variable_state + +- name: only one variable is left state must be changed + assert: + that: + - gitlab_instance_variable_state.changed + - gitlab_instance_variable_state.instance_variable.added|length == 0 + - gitlab_instance_variable_state.instance_variable.untouched|length == 0 + - gitlab_instance_variable_state.instance_variable.removed|length == 0 + - gitlab_instance_variable_state.instance_variable.updated|length == 1 + +- name: delete the last left variable + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + state: absent + variables: + - name: some + register: gitlab_instance_variable_state + +- name: no variable is left state must be changed + assert: + that: + - gitlab_instance_variable_state.changed + - gitlab_instance_variable_state.instance_variable.added|length == 0 + - gitlab_instance_variable_state.instance_variable.untouched|length == 0 + - gitlab_instance_variable_state.instance_variable.removed|length == 1 + - gitlab_instance_variable_state.instance_variable.updated|length == 0 + - gitlab_instance_variable_state.instance_variable.removed[0] == "some" + +- name: add one variable with variable_type file + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + variables: + - name: my_test_var + value: my_test_value + variable_type: file + purge: false + register: gitlab_instance_variable_state + +- name: append one variable state must be changed + assert: + that: + - gitlab_instance_variable_state.changed + - gitlab_instance_variable_state.instance_variable.added|length == 1 + - gitlab_instance_variable_state.instance_variable.untouched|length == 0 + - gitlab_instance_variable_state.instance_variable.removed|length == 0 + - gitlab_instance_variable_state.instance_variable.updated|length == 0 + # VALUE_SPECIFIED_IN_NO_LOG_PARAMETER + #- gitlab_instance_variable_state.instance_variable.added[0] == "my_test_var" + +- name: change variable_type attribute + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + variables: + - name: my_test_var + value: my_test_value + variable_type: env_var + register: gitlab_instance_variable_state + +- name: state must be changed + assert: + that: + - gitlab_instance_variable_state is changed + +- name: revert variable_type attribute + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + variables: + - name: my_test_var + value: my_test_value + variable_type: file + register: gitlab_instance_variable_state + +- name: state must be changed + assert: + that: + - gitlab_instance_variable_state is changed + +- name: delete the variable_type file variable + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + state: absent + variables: + - name: my_test_var + register: gitlab_instance_variable_state + +- name: no variable is left state must be changed + assert: + that: + - gitlab_instance_variable_state.changed + - gitlab_instance_variable_state.instance_variable.added|length == 0 + - gitlab_instance_variable_state.instance_variable.untouched|length == 0 + - gitlab_instance_variable_state.instance_variable.removed|length == 1 + - gitlab_instance_variable_state.instance_variable.updated|length == 0 + - gitlab_instance_variable_state.instance_variable.removed[0] == "my_test_var" + +- name: set complete page and purge existing ones + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + variables: + - name: page1_var01 + value: value + - name: page1_var02 + value: value + - name: page1_var03 + value: value + - name: page1_var04 + value: value + - name: page1_var05 + value: value + - name: page1_var06 + value: value + - name: page1_var07 + value: value + - name: page1_var08 + value: value + - name: page1_var09 + value: value + - name: page1_var10 + value: value + - name: page1_var11 + value: value + - name: page1_var12 + value: value + - name: page1_var13 + value: value + - name: page1_var14 + value: value + - name: page1_var15 + value: value + - name: page1_var16 + value: value + - name: page1_var17 + value: value + - name: page1_var18 + value: value + - name: page1_var19 + value: value + - name: page1_var20 + value: value + purge: true + register: gitlab_instance_variable_state + +- name: complete page added state must be changed + assert: + that: + - gitlab_instance_variable_state is changed + - gitlab_instance_variable_state.instance_variable.added|length == 20 + - gitlab_instance_variable_state.instance_variable.untouched|length == 0 + +- name: check that no variables are left + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + purge: true + register: gitlab_instance_variable_state + +- name: check that no variables are untouched state must be changed + assert: + that: + - gitlab_instance_variable_state.changed + - gitlab_instance_variable_state.instance_variable.added|length == 0 + - gitlab_instance_variable_state.instance_variable.untouched|length == 0 + - gitlab_instance_variable_state.instance_variable.removed|length == 20 + - gitlab_instance_variable_state.instance_variable.updated|length == 0 + +- name: throw error when state is present but no value is given + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + variables: + - name: no_value + register: gitlab_instance_variable_state + ignore_errors: true + +- name: verify fail + assert: + that: + - gitlab_instance_variable_state.failed + - gitlab_instance_variable_state is not changed + +- name: set a new variable to delete it later + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + purge: true + variables: + - name: delete_me + value: ansible + register: gitlab_instance_variable_state + +- name: verify the change + assert: + that: + - gitlab_instance_variable_state.changed + +- name: delete variable without referencing its value + gitlab_instance_variable: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_login_token }}" + state: absent + variables: + - name: delete_me + register: gitlab_instance_variable_state + +- name: verify deletion + assert: + that: + - gitlab_instance_variable_state.changed + - gitlab_instance_variable_state.instance_variable.removed|length == 1 diff --git a/ansible_collections/community/general/tests/integration/targets/gitlab_issue/aliases b/ansible_collections/community/general/tests/integration/targets/gitlab_issue/aliases new file mode 100644 index 000000000..4f4e3dcc9 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/gitlab_issue/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +gitlab/ci +disabled diff --git a/ansible_collections/community/general/tests/integration/targets/gitlab_issue/defaults/main.yml b/ansible_collections/community/general/tests/integration/targets/gitlab_issue/defaults/main.yml new file mode 100644 index 000000000..f94530fbf --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/gitlab_issue/defaults/main.yml @@ -0,0 +1,15 @@ +--- +# 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 + +gitlab_branch: ansible_test_branch +gitlab_project_name: ansible_test_project +gitlab_project_group: ansible_test_group +gitlab_host: ansible_test_host +gitlab_api_token: ansible_test_api_token +gitlab_labels: ansible_test_label +gitlab_milestone_search: ansible_test_milestone_search +gitlab_milestone_group_id: ansible_test_milestone_group_id +gitlab_assignee_ids: ansible_test_assignee_ids +gitlab_description_path: ansible_test_description_path
\ No newline at end of file diff --git a/ansible_collections/community/general/tests/integration/targets/gitlab_issue/files/description.md b/ansible_collections/community/general/tests/integration/targets/gitlab_issue/files/description.md new file mode 100644 index 000000000..f0ff12a77 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/gitlab_issue/files/description.md @@ -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 +--> + +### Description + +Issue test description
\ No newline at end of file diff --git a/ansible_collections/community/general/tests/integration/targets/gitlab_issue/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/gitlab_issue/tasks/main.yml new file mode 100644 index 000000000..af1416c3d --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/gitlab_issue/tasks/main.yml @@ -0,0 +1,150 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install required libs + pip: + name: python-gitlab + state: present + +- block: + - name: Create {{ gitlab_project_name }} project + gitlab_project: + api_url: "{{ gitlab_host }}" + validate_certs: true + api_token: "{{ gitlab_api_token }}" + name: "{{ gitlab_project_name }}" + group: "{{ gitlab_project_group }}" + default_branch: "{{ gitlab_branch }}" + initialize_with_readme: true + state: present + + - name: Create Issue + gitlab_issue: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + description: "Test description" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + state: present + title: "Ansible test issue" + register: gitlab_issue_create + + - name: Test Issue Created + assert: + that: + - gitlab_issue_create is changed + + - name: Create Issue ( Idempotency test ) + gitlab_issue: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + description: "Test description" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + state: present + title: "Ansible test issue" + register: gitlab_issue_create_idempotence + + - name: Test Create Issue is Idempotent + assert: + that: + - gitlab_issue_create_idempotence is not changed + + - name: Update Issue Test ( Additions ) + gitlab_issue: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + assignee_ids: "{{ gitlab_assignee_ids }}" + description_path: "{{ gitlab_description_path }}" + labels: "{{ gitlab_labels }}" + milestone_search: "{{ gitlab_milestone_search }}" + milestone_group_id: "{{ gitlab_milestone_group_id }}" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + state: present + title: "Ansible test issue" + register: gitlab_issue_update_additions + + - name: Test Issue Updated ( Additions ) + assert: + that: + - gitlab_issue_update_additions.issue.labels[0] == "{{ gitlab_labels[0] }}" + - gitlab_issue_update_additions.issue.assignees[0].username == "{{ gitlab_assignee_ids[0] }}" + - "'### Description\n\nIssue test description' in gitlab_issue_update_additions.issue.description" + - gitlab_issue_update_additions.issue.milestone.title == "{{ gitlab_milestone_search }}" + + - name: Update Issue Test ( Persistence ) + gitlab_issue: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + description_path: "{{ gitlab_description_path }}" + milestone_search: "{{ gitlab_milestone_search }}" + milestone_group_id: "{{ gitlab_milestone_group_id }}" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + state: present + title: "Ansible test issue" + register: gitlab_issue_update_persistence + + - name: Test issue Not Updated ( Persistence ) + assert: + that: + - gitlab_issue_update_persistence.issue.labels[0] == "{{ gitlab_labels[0] }}" + - gitlab_issue_update_persistence.issue.assignees[0].username == "{{ gitlab_assignee_ids[0] }}" + + - name: Update Issue Test ( Removals ) + gitlab_issue: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + assignee_ids: [] + description_path: "{{ gitlab_description_path }}" + labels: [] + milestone_search: "" + milestone_group_id: "" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + state: present + title: "Ansible test issue" + register: gitlab_issue_update_removal + + - name: Test issue updated + assert: + that: + - gitlab_issue_update_removal.issue.labels == [] + - gitlab_issue_update_removal.issue.assignees == [] + - gitlab_issue_update_removal.issue.milestone == None + + - name: Delete Issue + gitlab_issue: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_api_token }}" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + title: "Ansible test issue" + state: absent + register: gitlab_issue_delete + + - name: Test issue is deleted + assert: + that: + - gitlab_issue_delete is changed + + always: + - name: Delete Issue + gitlab_issue: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_api_token }}" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + title: "Ansible test issue" + state_filter: "opened" + state: absent + register: gitlab_issue_delete + - name: Clean up {{ gitlab_project_name }} + gitlab_project: + api_url: "{{ gitlab_host }}" + validate_certs: false + api_token: "{{ gitlab_api_token }}" + name: "{{ gitlab_project_name }}" + group: "{{ gitlab_project_group }}" + state: absent diff --git a/ansible_collections/community/general/tests/integration/targets/gitlab_label/README.md b/ansible_collections/community/general/tests/integration/targets/gitlab_label/README.md new file mode 100644 index 000000000..e27cb74c8 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/gitlab_label/README.md @@ -0,0 +1,19 @@ +<!-- +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 +--> + +# Gitlab integration tests + +1. to run integration tests locally, I've setup a podman pod with both gitlab-ee image and the testing image +2. gitlab's related information were taken from [here](https://docs.gitlab.com/ee/install/docker.html), about the variable it needs (hostname, ports, volumes); volumes were pre-made before launching the image +3. image that run integration tests is started with `podman run --rm -it --pod <pod_name> --name <image_name> --network=host --volume <path_to_git_repo>/ansible_community/community.general:<container_path_to>/workspace/ansible_collections/community/general quay.io/ansible/azure-pipelines-test-container:4.0.1` +4. into the testing image, run +```sh +pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check +cd <container_path_to>/workspace/ansible_collections/community/general +ansible-test integration gitlab_label -vvv +``` + +While debugging with `q` package, open a second terminal and run `podman exec -it <image_name> /bin/bash` and inside it do `tail -f /tmp/q` . diff --git a/ansible_collections/community/general/tests/integration/targets/gitlab_label/aliases b/ansible_collections/community/general/tests/integration/targets/gitlab_label/aliases new file mode 100644 index 000000000..4f4e3dcc9 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/gitlab_label/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +gitlab/ci +disabled diff --git a/ansible_collections/community/general/tests/integration/targets/gitlab_label/defaults/main.yml b/ansible_collections/community/general/tests/integration/targets/gitlab_label/defaults/main.yml new file mode 100644 index 000000000..315cbd77f --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/gitlab_label/defaults/main.yml @@ -0,0 +1,17 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +gitlab_project_name: ansible_test_project +gitlab_host: ansible_test_host +gitlab_api_token: ansible_test_api_token +gitlab_project_group: ansible_test_group +gitlab_branch: ansible_test_branch +gitlab_first_label: ansible_test_label +gitlab_first_label_color: "#112233" +gitlab_first_label_description: "label description" +gitlab_first_label_priority: 10 +gitlab_second_label: ansible_test_second_label +gitlab_second_label_color: "#445566" +gitlab_second_label_new_name: ansible_test_second_label_new_name
\ No newline at end of file diff --git a/ansible_collections/community/general/tests/integration/targets/gitlab_label/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/gitlab_label/tasks/main.yml new file mode 100644 index 000000000..880b72ceb --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/gitlab_label/tasks/main.yml @@ -0,0 +1,463 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install required libs + pip: + name: python-gitlab + state: present + +- block: +### +### Group label +### + + - name: Create {{ gitlab_project_group }} + gitlab_group: + api_url: "{{ gitlab_host }}" + validate_certs: true + api_token: "{{ gitlab_api_token }}" + name: "{{ gitlab_project_group }}" + state: present + + - name: Purge all group labels for check_mode test + gitlab_label: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_api_token }}" + group: "{{ gitlab_project_group }}" + purge: true + + - name: Group label - Add a label in check_mode + gitlab_label: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + group: "{{ gitlab_project_group }}" + labels: + - name: "{{ gitlab_second_label }}" + color: "{{ gitlab_second_label_color }}" + check_mode: true + register: gitlab_group_label_state + + - name: Group label - Check_mode state must be changed + assert: + that: + - gitlab_group_label_state is changed + + - name: Group label - Create label {{ gitlab_first_label }} and {{ gitlab_second_label }} + gitlab_label: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + group: "{{ gitlab_project_group }}" + labels: + - name: "{{ gitlab_first_label }}" + color: "{{ gitlab_first_label_color }}" + description: "{{ gitlab_first_label_description }}" + priority: "{{ gitlab_first_label_priority }}" + - name: "{{ gitlab_second_label }}" + color: "{{ gitlab_second_label_color }}" + state: present + register: gitlab_group_label_create + + - name: Group label - Test Label Created + assert: + that: + - gitlab_group_label_create is changed + - gitlab_group_label_create.labels.added|length == 2 + - gitlab_group_label_create.labels.untouched|length == 0 + - gitlab_group_label_create.labels.removed|length == 0 + - gitlab_group_label_create.labels.updated|length == 0 + - gitlab_group_label_create.labels.added[0] == "{{ gitlab_first_label }}" + - gitlab_group_label_create.labels.added[1] == "{{ gitlab_second_label }}" + + - name: Group label - Create Label ( Idempotency test ) + gitlab_label: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + group: "{{ gitlab_project_group }}" + labels: + - name: "{{ gitlab_first_label }}" + color: "{{ gitlab_first_label_color }}" + description: "{{ gitlab_first_label_description }}" + priority: "{{ gitlab_first_label_priority }}" + state: present + register: gitlab_group_label_create_idempotence + + - name: Group label - Test Create Label is Idempotent + assert: + that: + - gitlab_group_label_create_idempotence is not changed + + - name: Group label - Update Label {{ gitlab_first_label }} changing color + gitlab_label: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + group: "{{ gitlab_project_group }}" + labels: + - name: "{{ gitlab_first_label }}" + color: "{{ gitlab_second_label_color }}" + state: present + register: gitlab_group_label_update + + - name: Group label - Test Label Updated + assert: + that: + - gitlab_group_label_update.labels.added|length == 0 + - gitlab_group_label_update.labels.untouched|length == 0 + - gitlab_group_label_update.labels.removed|length == 0 + - gitlab_group_label_update.labels.updated|length == 1 + - gitlab_group_label_update.labels.updated[0] == "{{ gitlab_first_label }}" + + - name: Group label - Change label {{ gitlab_second_label }} name to {{ gitlab_second_label_new_name }} + gitlab_label: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + group: "{{ gitlab_project_group }}" + labels: + - name: "{{ gitlab_second_label }}" + new_name: "{{ gitlab_second_label_new_name }}" + state: present + register: gitlab_group_label_new_name + + - name: Group label - Test Label name changed + assert: + that: + - gitlab_group_label_new_name.labels.added|length == 0 + - gitlab_group_label_new_name.labels.untouched|length == 0 + - gitlab_group_label_new_name.labels.removed|length == 0 + - gitlab_group_label_new_name.labels.updated|length == 1 + - gitlab_group_label_new_name.labels.updated[0] == "{{ gitlab_second_label }}" + + - name: Group label - Change label name back to {{ gitlab_second_label }} + gitlab_label: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + group: "{{ gitlab_project_group }}" + labels: + - name: "{{ gitlab_second_label_new_name }}" + new_name: "{{ gitlab_second_label }}" + state: present + register: gitlab_group_label_orig_name + + - name: Group label - Test Label name changed back + assert: + that: + - gitlab_group_label_orig_name.labels.added|length == 0 + - gitlab_group_label_orig_name.labels.untouched|length == 0 + - gitlab_group_label_orig_name.labels.removed|length == 0 + - gitlab_group_label_orig_name.labels.updated|length == 1 + - gitlab_group_label_orig_name.labels.updated[0] == "{{ gitlab_second_label_new_name }}" + + - name: Group label - Update Label Test ( Additions ) + gitlab_label: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + group: "{{ gitlab_project_group }}" + labels: + - name: "{{ gitlab_second_label }}" + description: "{{ gitlab_first_label_description }}" + priority: "{{ gitlab_first_label_priority }}" + state: present + register: gitlab_group_label_update_additions + + - name: Group label - Test Label Updated ( Additions ) + assert: + that: + - gitlab_group_label_update_additions.labels.added|length == 0 + - gitlab_group_label_update_additions.labels.untouched|length == 0 + - gitlab_group_label_update_additions.labels.removed|length == 0 + - gitlab_group_label_update_additions.labels.updated|length == 1 + - gitlab_group_label_update_additions.labels.updated[0] == "{{ gitlab_second_label }}" + + - name: Group label - Delete Label {{ gitlab_second_label }} + gitlab_label: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + group: "{{ gitlab_project_group }}" + labels: + - name: "{{ gitlab_second_label }}" + state: absent + register: gitlab_group_label_delete + + - name: Group label - Test label is deleted + assert: + that: + - gitlab_group_label_delete is changed + - gitlab_group_label_delete.labels.added|length == 0 + - gitlab_group_label_delete.labels.untouched|length == 0 + - gitlab_group_label_delete.labels.removed|length == 1 + - gitlab_group_label_delete.labels.updated|length == 0 + - gitlab_group_label_delete.labels.removed[0] == "{{ gitlab_second_label }}" + + - name: Group label - Create label {{ gitlab_second_label }} again purging the other + gitlab_label: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + group: "{{ gitlab_project_group }}" + purge: true + labels: + - name: "{{ gitlab_second_label }}" + color: "{{ gitlab_second_label_color }}" + state: present + register: gitlab_group_label_create_purging + + - name: Group label - Test Label Created again + assert: + that: + - gitlab_group_label_create_purging is changed + - gitlab_group_label_create_purging.labels.added|length == 1 + - gitlab_group_label_create_purging.labels.untouched|length == 0 + - gitlab_group_label_create_purging.labels.removed|length == 1 + - gitlab_group_label_create_purging.labels.updated|length == 0 + - gitlab_group_label_create_purging.labels.added[0] == "{{ gitlab_second_label }}" + - gitlab_group_label_create_purging.labels.removed[0] == "{{ gitlab_first_label }}" + +### +### Project label +### + + - name: Create {{ gitlab_project_name }} + gitlab_project: + api_url: "{{ gitlab_host }}" + validate_certs: true + api_token: "{{ gitlab_api_token }}" + name: "{{ gitlab_project_name }}" + group: "{{ gitlab_project_group }}" + default_branch: "{{ gitlab_branch }}" + initialize_with_readme: true + state: present + + - name: Purge all labels for check_mode test + gitlab_label: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_api_token }}" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + purge: true + + - name: Add a label in check_mode + gitlab_label: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + labels: + - name: "{{ gitlab_second_label }}" + color: "{{ gitlab_second_label_color }}" + check_mode: true + register: gitlab_first_label_state + + - name: Check_mode state must be changed + assert: + that: + - gitlab_first_label_state is changed + + - name: Create label {{ gitlab_first_label }} and {{ gitlab_second_label }} + gitlab_label: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + labels: + - name: "{{ gitlab_first_label }}" + color: "{{ gitlab_first_label_color }}" + description: "{{ gitlab_first_label_description }}" + priority: "{{ gitlab_first_label_priority }}" + - name: "{{ gitlab_second_label }}" + color: "{{ gitlab_second_label_color }}" + state: present + register: gitlab_first_label_create + + - name: Test Label Created + assert: + that: + - gitlab_first_label_create is changed + - gitlab_first_label_create.labels.added|length == 2 + - gitlab_first_label_create.labels.untouched|length == 0 + - gitlab_first_label_create.labels.removed|length == 0 + - gitlab_first_label_create.labels.updated|length == 0 + - gitlab_first_label_create.labels.added[0] == "{{ gitlab_first_label }}" + - gitlab_first_label_create.labels.added[1] == "{{ gitlab_second_label }}" + + - name: Create Label ( Idempotency test ) + gitlab_label: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + labels: + - name: "{{ gitlab_first_label }}" + color: "{{ gitlab_first_label_color }}" + description: "{{ gitlab_first_label_description }}" + priority: "{{ gitlab_first_label_priority }}" + state: present + register: gitlab_first_label_create_idempotence + + - name: Test Create Label is Idempotent + assert: + that: + - gitlab_first_label_create_idempotence is not changed + + - name: Update Label {{ gitlab_first_label }} changing color + gitlab_label: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + labels: + - name: "{{ gitlab_first_label }}" + color: "{{ gitlab_second_label_color }}" + state: present + register: gitlab_first_label_update + + - name: Test Label Updated + assert: + that: + - gitlab_first_label_update.labels.added|length == 0 + - gitlab_first_label_update.labels.untouched|length == 0 + - gitlab_first_label_update.labels.removed|length == 0 + - gitlab_first_label_update.labels.updated|length == 1 + - gitlab_first_label_update.labels.updated[0] == "{{ gitlab_first_label }}" + + - name: Change label {{ gitlab_second_label }} name to {{ gitlab_second_label_new_name }} + gitlab_label: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + labels: + - name: "{{ gitlab_second_label }}" + new_name: "{{ gitlab_second_label_new_name }}" + state: present + register: gitlab_first_label_new_name + + - name: Test Label name changed + assert: + that: + - gitlab_first_label_new_name.labels.added|length == 0 + - gitlab_first_label_new_name.labels.untouched|length == 0 + - gitlab_first_label_new_name.labels.removed|length == 0 + - gitlab_first_label_new_name.labels.updated|length == 1 + - gitlab_first_label_new_name.labels.updated[0] == "{{ gitlab_second_label }}" + + - name: Change label name back to {{ gitlab_second_label }} + gitlab_label: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + labels: + - name: "{{ gitlab_second_label_new_name }}" + new_name: "{{ gitlab_second_label }}" + state: present + register: gitlab_first_label_orig_name + + - name: Test Label name changed back + assert: + that: + - gitlab_first_label_orig_name.labels.added|length == 0 + - gitlab_first_label_orig_name.labels.untouched|length == 0 + - gitlab_first_label_orig_name.labels.removed|length == 0 + - gitlab_first_label_orig_name.labels.updated|length == 1 + - gitlab_first_label_orig_name.labels.updated[0] == "{{ gitlab_second_label_new_name }}" + + - name: Update Label Test ( Additions ) + gitlab_label: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + labels: + - name: "{{ gitlab_second_label }}" + description: "{{ gitlab_first_label_description }}" + priority: "{{ gitlab_first_label_priority }}" + state: present + register: gitlab_first_label_update_additions + + - name: Test Label Updated ( Additions ) + assert: + that: + - gitlab_first_label_update_additions.labels.added|length == 0 + - gitlab_first_label_update_additions.labels.untouched|length == 0 + - gitlab_first_label_update_additions.labels.removed|length == 0 + - gitlab_first_label_update_additions.labels.updated|length == 1 + - gitlab_first_label_update_additions.labels.updated[0] == "{{ gitlab_second_label }}" + + - name: Delete Label {{ gitlab_second_label }} + gitlab_label: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + labels: + - name: "{{ gitlab_second_label }}" + state: absent + register: gitlab_first_label_delete + + - name: Test label is deleted + assert: + that: + - gitlab_first_label_delete is changed + - gitlab_first_label_delete.labels.added|length == 0 + - gitlab_first_label_delete.labels.untouched|length == 0 + - gitlab_first_label_delete.labels.removed|length == 1 + - gitlab_first_label_delete.labels.updated|length == 0 + - gitlab_first_label_delete.labels.removed[0] == "{{ gitlab_second_label }}" + + - name: Create label {{ gitlab_second_label }} again purging the other + gitlab_label: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + purge: true + labels: + - name: "{{ gitlab_second_label }}" + color: "{{ gitlab_second_label_color }}" + state: present + register: gitlab_first_label_create_purging + + - name: Test Label Created again + assert: + that: + - gitlab_first_label_create_purging is changed + - gitlab_first_label_create_purging.labels.added|length == 1 + - gitlab_first_label_create_purging.labels.untouched|length == 0 + - gitlab_first_label_create_purging.labels.removed|length == 1 + - gitlab_first_label_create_purging.labels.updated|length == 0 + - gitlab_first_label_create_purging.labels.added[0] == "{{ gitlab_second_label }}" + - gitlab_first_label_create_purging.labels.removed[0] == "{{ gitlab_first_label }}" + + always: + - name: Delete Labels + gitlab_label: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + purge: true + labels: + - name: "{{ gitlab_first_label }}" + - name: "{{ gitlab_second_label }}" + state: absent + register: gitlab_first_label_always_delete + + - name: Test label are deleted + assert: + that: + - gitlab_first_label_always_delete is changed + - gitlab_first_label_always_delete.labels.added|length == 0 + - gitlab_first_label_always_delete.labels.untouched|length == 0 + - gitlab_first_label_always_delete.labels.removed|length > 0 + - gitlab_first_label_always_delete.labels.updated|length == 0 + + - name: Clean up {{ gitlab_project_name }} + gitlab_project: + api_url: "{{ gitlab_host }}" + validate_certs: false + api_token: "{{ gitlab_api_token }}" + name: "{{ gitlab_project_name }}" + group: "{{ gitlab_project_group }}" + state: absent + + - name: Clean up {{ gitlab_project_group }} + gitlab_group: + api_url: "{{ gitlab_host }}" + validate_certs: true + api_token: "{{ gitlab_api_token }}" + name: "{{ gitlab_project_group }}" + state: absent
\ No newline at end of file diff --git a/ansible_collections/community/general/tests/integration/targets/gitlab_merge_request/aliases b/ansible_collections/community/general/tests/integration/targets/gitlab_merge_request/aliases new file mode 100644 index 000000000..4f4e3dcc9 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/gitlab_merge_request/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +gitlab/ci +disabled diff --git a/ansible_collections/community/general/tests/integration/targets/gitlab_merge_request/defaults/main.yml b/ansible_collections/community/general/tests/integration/targets/gitlab_merge_request/defaults/main.yml new file mode 100644 index 000000000..eb27b0b68 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/gitlab_merge_request/defaults/main.yml @@ -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 + +gitlab_source_branch: ansible_test_source_branch +gitlab_target_branch: ansible_test_target_project +gitlab_project_name: ansible_test_project +gitlab_project_group: ansible_test_group +gitlab_host: ansible_test_host +gitlab_api_token: ansible_test_api_token +gitlab_labels: ansible_test_label +gitlab_assignee_ids: ansible_test_assignee_ids +gitlab_description_path: ansible_test_description_path
\ No newline at end of file diff --git a/ansible_collections/community/general/tests/integration/targets/gitlab_merge_request/files/description.md b/ansible_collections/community/general/tests/integration/targets/gitlab_merge_request/files/description.md new file mode 100644 index 000000000..3f662eff8 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/gitlab_merge_request/files/description.md @@ -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 +--> + +### Description + +Merge Request test description
\ No newline at end of file diff --git a/ansible_collections/community/general/tests/integration/targets/gitlab_merge_request/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/gitlab_merge_request/tasks/main.yml new file mode 100644 index 000000000..18da900a2 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/gitlab_merge_request/tasks/main.yml @@ -0,0 +1,129 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install required libs + pip: + name: python-gitlab + state: present + +- block: + - name: Create {{ gitlab_project_name }} + gitlab_project: + api_url: "{{ gitlab_host }}" + validate_certs: true + api_token: "{{ gitlab_api_token }}" + name: "{{ gitlab_project_name }}" + group: "{{ gitlab_project_group }}" + default_branch: "{{ gitlab_target_branch }}" + initialize_with_readme: true + state: present + + - name: Create branch {{ gitlab_source_branch }} + gitlab_branch: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_api_token }}" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + branch: "{{ gitlab_source_branch }}" + ref_branch: "{{ gitlab_target_branch }}" + state: present + + - name: Create Merge Request + gitlab_merge_request: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_api_token }}" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + source_branch: "{{gitlab_source_branch}}" + target_branch: "{{gitlab_target_branch}}" + title: "Ansible test merge request" + description: "Test description" + labels: "" + state_filter: "opened" + assignee_ids: "" + reviewer_ids: "" + remove_source_branch: True + state: present + register: gitlab_merge_request_create + + - name: Test Merge Request Created + assert: + that: + - gitlab_merge_request_create is changed + + - name: Create Merge Request ( Idempotency test ) + gitlab_merge_request: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_api_token }}" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + source_branch: "{{gitlab_source_branch}}" + target_branch: "{{gitlab_target_branch}}" + title: "Ansible test merge request" + description: "Test description" + labels: "" + state_filter: "opened" + assignee_ids: "" + reviewer_ids: "" + remove_source_branch: True + state: present + register: gitlab_merge_request_create_idempotence + + - name: Test module is idempotent + assert: + that: + - gitlab_merge_request_create_idempotence is not changed + + - name: Update Merge Request Test + gitlab_merge_request: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_api_token }}" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + source_branch: "{{gitlab_source_branch}}" + target_branch: "{{gitlab_target_branch}}" + title: "Ansible test merge request" + description_path: "{{gitlab_description_path}}" + labels: "{{ gitlab_labels }}" + state_filter: "opened" + assignee_ids: "{{ gitlab_assignee_ids }}" + reviewer_ids: "" + remove_source_branch: True + state: present + register: gitlab_merge_request_udpate + + - name: Test merge request updated + assert: + that: + - gitlab_merge_request_udpate.mr.labels[0] == "{{ gitlab_labels }}" + - gitlab_merge_request_udpate.mr.assignees[0].username == "{{ gitlab_assignee_ids }}" + - "'### Description\n\nMerge Request test description' in gitlab_merge_request_udpate.mr.description" + + - name: Delete Merge Request + gitlab_merge_request: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_api_token }}" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + source_branch: "{{gitlab_source_branch}}" + target_branch: "{{gitlab_target_branch}}" + title: "Ansible test merge request" + state: absent + register: gitlab_merge_request_delete + + - name: Test merge request is deleted + assert: + that: + - gitlab_merge_request_delete is changed + + always: + - name: Clean up {{ gitlab_project_name }} + gitlab_project: + api_url: "{{ gitlab_host }}" + validate_certs: false + api_token: "{{ gitlab_api_token }}" + name: "{{ gitlab_project_name }}" + group: "{{ gitlab_project_group }}" + state: absent diff --git a/ansible_collections/community/general/tests/integration/targets/gitlab_milestone/README.md b/ansible_collections/community/general/tests/integration/targets/gitlab_milestone/README.md new file mode 100644 index 000000000..95bb3410b --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/gitlab_milestone/README.md @@ -0,0 +1,19 @@ +<!-- +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 +--> + +# Gitlab integration tests + +1. to run integration tests locally, I've setup a podman pod with both gitlab-ee image and the testing image +2. gitlab's related information were taken from [here](https://docs.gitlab.com/ee/install/docker.html), about the variable it needs (hostname, ports, volumes); volumes were pre-made before launching the image +3. image that run integration tests is started with `podman run --rm -it --pod <pod_name> --name <image_name> --network=host --volume <path_to_git_repo>/ansible_community/community.general:<container_path_to>/workspace/ansible_collections/community/general quay.io/ansible/azure-pipelines-test-container:4.0.1` +4. into the testing image, run +```sh +pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check +cd <container_path_to>/workspace/ansible_collections/community/general +ansible-test integration gitlab_milestone -vvv +``` + +While debugging with `q` package, open a second terminal and run `podman exec -it <image_name> /bin/bash` and inside it do `tail -f /tmp/q` . diff --git a/ansible_collections/community/general/tests/integration/targets/gitlab_milestone/aliases b/ansible_collections/community/general/tests/integration/targets/gitlab_milestone/aliases new file mode 100644 index 000000000..1d485e509 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/gitlab_milestone/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +gitlab/ci +disabled
\ No newline at end of file diff --git a/ansible_collections/community/general/tests/integration/targets/gitlab_milestone/defaults/main.yml b/ansible_collections/community/general/tests/integration/targets/gitlab_milestone/defaults/main.yml new file mode 100644 index 000000000..d11001295 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/gitlab_milestone/defaults/main.yml @@ -0,0 +1,18 @@ +--- +# 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 + +gitlab_project_name: ansible_test_project +gitlab_host: ansible_test_host +gitlab_api_token: ansible_test_api_token +gitlab_project_group: ansible_test_group +gitlab_branch: ansible_test_branch +gitlab_first_milestone: ansible_test_milestone +gitlab_first_milestone_description: "milestone description" +gitlab_first_milestone_start_date: "2024-01-01" +gitlab_first_milestone_due_date: "2024-12-31" +gitlab_first_milestone_new_start_date: "2024-05-01" +gitlab_first_milestone_new_due_date: "2024-10-31" +gitlab_second_milestone: ansible_test_second_milestone +gitlab_second_milestone_description: "new milestone" diff --git a/ansible_collections/community/general/tests/integration/targets/gitlab_milestone/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/gitlab_milestone/tasks/main.yml new file mode 100644 index 000000000..ce78c2eef --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/gitlab_milestone/tasks/main.yml @@ -0,0 +1,388 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install required libs + pip: + name: python-gitlab + state: present + +- block: +### +### Group milestone +### + - name: Create {{ gitlab_project_group }} + gitlab_group: + api_url: "{{ gitlab_host }}" + validate_certs: true + api_token: "{{ gitlab_api_token }}" + name: "{{ gitlab_project_group }}" + state: present + + - name: Purge all group milestones for check_mode test + gitlab_milestone: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_api_token }}" + group: "{{ gitlab_project_group }}" + purge: true + + - name: Group milestone - Add a milestone in check_mode + gitlab_milestone: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + group: "{{ gitlab_project_group }}" + milestones: + - title: "{{ gitlab_second_milestone }}" + check_mode: true + register: gitlab_group_milestone_state + + - name: Group milestone - Check_mode state must be changed + assert: + that: + - gitlab_group_milestone_state is changed + + - name: Purge all group milestones for project milestone test - cannot exist with same name + gitlab_milestone: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_api_token }}" + group: "{{ gitlab_project_group }}" + purge: true + register: gitlab_group_milestone_purged + + - name: Group milestone - Create milestone {{ gitlab_first_milestone }} and {{ gitlab_second_milestone }} + gitlab_milestone: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + group: "{{ gitlab_project_group }}" + milestones: + - title: "{{ gitlab_first_milestone }}" + start_date: "{{ gitlab_first_milestone_start_date }}" + due_date: "{{ gitlab_first_milestone_due_date }}" + - title: "{{ gitlab_second_milestone }}" + state: present + register: gitlab_group_milestone_create + + - name: Group milestone - Test milestone Created + assert: + that: + - gitlab_group_milestone_create is changed + - gitlab_group_milestone_create.milestones.added|length == 2 + - gitlab_group_milestone_create.milestones.untouched|length == 0 + - gitlab_group_milestone_create.milestones.removed|length == 0 + - gitlab_group_milestone_create.milestones.updated|length == 0 + - gitlab_group_milestone_create.milestones.added[0] == "{{ gitlab_first_milestone }}" + - gitlab_group_milestone_create.milestones.added[1] == "{{ gitlab_second_milestone }}" + + - name: Group milestone - Create milestone ( Idempotency test ) + gitlab_milestone: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + group: "{{ gitlab_project_group }}" + milestones: + - title: "{{ gitlab_first_milestone }}" + start_date: "{{ gitlab_first_milestone_start_date }}" + due_date: "{{ gitlab_first_milestone_due_date }}" + state: present + register: gitlab_group_milestone_create_idempotence + + - name: Group milestone - Test Create milestone is Idempotent + assert: + that: + - gitlab_group_milestone_create_idempotence is not changed + + - name: Group milestone - Update milestone {{ gitlab_first_milestone }} changing dates + gitlab_milestone: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + group: "{{ gitlab_project_group }}" + milestones: + - title: "{{ gitlab_first_milestone }}" + start_date: "{{ gitlab_first_milestone_new_start_date }}" + due_date: "{{ gitlab_first_milestone_new_due_date }}" + state: present + register: gitlab_group_milestone_update + + - name: Group milestone - Test milestone Updated + assert: + that: + - gitlab_group_milestone_update.milestones.added|length == 0 + - gitlab_group_milestone_update.milestones.untouched|length == 0 + - gitlab_group_milestone_update.milestones.removed|length == 0 + - gitlab_group_milestone_update.milestones.updated|length == 1 + - gitlab_group_milestone_update.milestones.updated[0] == "{{ gitlab_first_milestone }}" + + - name: Group milestone - Update milestone Test ( Additions ) + gitlab_milestone: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + group: "{{ gitlab_project_group }}" + milestones: + - title: "{{ gitlab_second_milestone }}" + description: "{{ gitlab_first_milestone_description }}" + state: present + register: gitlab_group_milestone_update_additions + + - name: Group milestone - Test milestone Updated ( Additions ) + assert: + that: + - gitlab_group_milestone_update_additions.milestones.added|length == 0 + - gitlab_group_milestone_update_additions.milestones.untouched|length == 0 + - gitlab_group_milestone_update_additions.milestones.removed|length == 0 + - gitlab_group_milestone_update_additions.milestones.updated|length == 1 + - gitlab_group_milestone_update_additions.milestones.updated[0] == "{{ gitlab_second_milestone }}" + + - name: Group milestone - Delete milestone {{ gitlab_second_milestone }} + gitlab_milestone: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + group: "{{ gitlab_project_group }}" + milestones: + - title: "{{ gitlab_second_milestone }}" + state: absent + register: gitlab_group_milestone_delete + + - name: Group milestone - Test milestone is deleted + assert: + that: + - gitlab_group_milestone_delete is changed + - gitlab_group_milestone_delete.milestones.added|length == 0 + - gitlab_group_milestone_delete.milestones.untouched|length == 0 + - gitlab_group_milestone_delete.milestones.removed|length == 1 + - gitlab_group_milestone_delete.milestones.updated|length == 0 + - gitlab_group_milestone_delete.milestones.removed[0] == "{{ gitlab_second_milestone }}" + + - name: Group milestone - Create group milestone {{ gitlab_second_milestone }} again purging the other + gitlab_milestone: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + group: "{{ gitlab_project_group }}" + purge: true + milestones: + - title: "{{ gitlab_second_milestone }}" + state: present + register: gitlab_group_milestone_create_purging + + - name: Group milestone - Test milestone Created again + assert: + that: + - gitlab_group_milestone_create_purging is changed + - gitlab_group_milestone_create_purging.milestones.added|length == 1 + - gitlab_group_milestone_create_purging.milestones.untouched|length == 0 + - gitlab_group_milestone_create_purging.milestones.removed|length == 1 + - gitlab_group_milestone_create_purging.milestones.updated|length == 0 + - gitlab_group_milestone_create_purging.milestones.added[0] == "{{ gitlab_second_milestone }}" + - gitlab_group_milestone_create_purging.milestones.removed[0] == "{{ gitlab_first_milestone }}" + +### +### Project milestone +### + - name: Purge all group milestones for project milestone test - cannot exist with same name + gitlab_milestone: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_api_token }}" + group: "{{ gitlab_project_group }}" + purge: true + register: gitlab_group_milestone_purged + + - name: Create {{ gitlab_project_name }} + gitlab_project: + api_url: "{{ gitlab_host }}" + validate_certs: true + api_token: "{{ gitlab_api_token }}" + name: "{{ gitlab_project_name }}" + group: "{{ gitlab_project_group }}" + default_branch: "{{ gitlab_branch }}" + initialize_with_readme: true + state: present + + - name: Purge all milestones for check_mode test + gitlab_milestone: + api_url: "{{ gitlab_host }}" + api_token: "{{ gitlab_api_token }}" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + purge: true + + - name: Add a milestone in check_mode + gitlab_milestone: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + milestones: + - title: "{{ gitlab_second_milestone }}" + description: "{{ gitlab_second_milestone_description }}" + check_mode: true + register: gitlab_first_milestone_state + + - name: Check_mode state must be changed + assert: + that: + - gitlab_first_milestone_state is changed + + - name: Create milestone {{ gitlab_first_milestone }} and {{ gitlab_second_milestone }} + gitlab_milestone: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + milestones: + - title: "{{ gitlab_first_milestone }}" + start_date: "{{ gitlab_first_milestone_start_date }}" + due_date: "{{ gitlab_first_milestone_due_date }}" + - title: "{{ gitlab_second_milestone }}" + state: present + register: gitlab_milestones_create + + - name: Test milestone Created + assert: + that: + - gitlab_milestones_create is changed + - gitlab_milestones_create.milestones.added|length == 2 + - gitlab_milestones_create.milestones.untouched|length == 0 + - gitlab_milestones_create.milestones.removed|length == 0 + - gitlab_milestones_create.milestones.updated|length == 0 + - gitlab_milestones_create.milestones.added[0] == "{{ gitlab_first_milestone }}" + - gitlab_milestones_create.milestones.added[1] == "{{ gitlab_second_milestone }}" + + - name: Create milestone ( Idempotency test ) + gitlab_milestone: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + milestones: + - title: "{{ gitlab_first_milestone }}" + start_date: "{{ gitlab_first_milestone_start_date }}" + due_date: "{{ gitlab_first_milestone_due_date }}" + state: present + register: gitlab_first_milestone_create_idempotence + + - name: Test Create milestone is Idempotent + assert: + that: + - gitlab_first_milestone_create_idempotence is not changed + + - name: Update milestone {{ gitlab_first_milestone }} changing dates + gitlab_milestone: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + milestones: + - title: "{{ gitlab_first_milestone }}" + start_date: "{{ gitlab_first_milestone_new_start_date }}" + due_date: "{{ gitlab_first_milestone_new_due_date }}" + state: present + register: gitlab_first_milestone_update + + - name: Test milestone Updated + assert: + that: + - gitlab_first_milestone_update.milestones.added|length == 0 + - gitlab_first_milestone_update.milestones.untouched|length == 0 + - gitlab_first_milestone_update.milestones.removed|length == 0 + - gitlab_first_milestone_update.milestones.updated|length == 1 + - gitlab_first_milestone_update.milestones.updated[0] == "{{ gitlab_first_milestone }}" + + - name: Update milestone Test ( Additions ) + gitlab_milestone: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + milestones: + - title: "{{ gitlab_second_milestone }}" + description: "{{ gitlab_second_milestone_description }}" + state: present + register: gitlab_first_milestone_update_additions + + - name: Test milestone Updated ( Additions ) + assert: + that: + - gitlab_first_milestone_update_additions.milestones.added|length == 0 + - gitlab_first_milestone_update_additions.milestones.untouched|length == 0 + - gitlab_first_milestone_update_additions.milestones.removed|length == 0 + - gitlab_first_milestone_update_additions.milestones.updated|length == 1 + - gitlab_first_milestone_update_additions.milestones.updated[0] == "{{ gitlab_second_milestone }}" + + - name: Delete milestone {{ gitlab_second_milestone }} + gitlab_milestone: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + milestones: + - title: "{{ gitlab_second_milestone }}" + state: absent + register: gitlab_first_milestone_delete + + - name: Test milestone is deleted + assert: + that: + - gitlab_first_milestone_delete is changed + - gitlab_first_milestone_delete.milestones.added|length == 0 + - gitlab_first_milestone_delete.milestones.untouched|length == 0 + - gitlab_first_milestone_delete.milestones.removed|length == 1 + - gitlab_first_milestone_delete.milestones.updated|length == 0 + - gitlab_first_milestone_delete.milestones.removed[0] == "{{ gitlab_second_milestone }}" + + - name: Create milestone {{ gitlab_second_milestone }} again purging the other + gitlab_milestone: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + purge: true + milestones: + - title: "{{ gitlab_second_milestone }}" + state: present + register: gitlab_first_milestone_create_purging + + - name: Test milestone Created again + assert: + that: + - gitlab_first_milestone_create_purging is changed + - gitlab_first_milestone_create_purging.milestones.added|length == 1 + - gitlab_first_milestone_create_purging.milestones.untouched|length == 0 + - gitlab_first_milestone_create_purging.milestones.removed|length == 1 + - gitlab_first_milestone_create_purging.milestones.updated|length == 0 + - gitlab_first_milestone_create_purging.milestones.added[0] == "{{ gitlab_second_milestone }}" + - gitlab_first_milestone_create_purging.milestones.removed[0] == "{{ gitlab_first_milestone }}" + + always: + - name: Delete milestones + gitlab_milestone: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_host }}" + project: "{{ gitlab_project_group }}/{{ gitlab_project_name }}" + purge: true + milestones: + - title: "{{ gitlab_first_milestone }}" + - title: "{{ gitlab_second_milestone }}" + state: absent + register: gitlab_first_milestone_always_delete + + - name: Test milestone are deleted + assert: + that: + - gitlab_first_milestone_always_delete is changed + - gitlab_first_milestone_always_delete.milestones.added|length == 0 + - gitlab_first_milestone_always_delete.milestones.untouched|length == 0 + - gitlab_first_milestone_always_delete.milestones.removed|length > 0 + - gitlab_first_milestone_always_delete.milestones.updated|length == 0 + + - name: Clean up {{ gitlab_project_name }} + gitlab_project: + api_url: "{{ gitlab_host }}" + validate_certs: false + api_token: "{{ gitlab_api_token }}" + name: "{{ gitlab_project_name }}" + group: "{{ gitlab_project_group }}" + state: absent + + - name: Clean up {{ gitlab_project_group }} + gitlab_group: + api_url: "{{ gitlab_host }}" + validate_certs: true + api_token: "{{ gitlab_api_token }}" + name: "{{ gitlab_project_group }}" + state: absent
\ No newline at end of file diff --git a/ansible_collections/community/general/tests/integration/targets/gitlab_project_access_token/aliases b/ansible_collections/community/general/tests/integration/targets/gitlab_project_access_token/aliases new file mode 100644 index 000000000..fc0e157c9 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/gitlab_project_access_token/aliases @@ -0,0 +1,7 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/posix/1 +gitlab/ci +disabled diff --git a/ansible_collections/community/general/tests/integration/targets/gitlab_project_access_token/defaults/main.yml b/ansible_collections/community/general/tests/integration/targets/gitlab_project_access_token/defaults/main.yml new file mode 100644 index 000000000..579584d62 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/gitlab_project_access_token/defaults/main.yml @@ -0,0 +1,15 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Copyright (c) 2024, Zoran Krleza <zoran.krleza@true-north.hr> +# 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 + +gitlab_api_token: +gitlab_api_url: +gitlab_validate_certs: false +gitlab_project_name: +gitlab_token_name: diff --git a/ansible_collections/community/general/tests/integration/targets/gitlab_project_access_token/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/gitlab_project_access_token/tasks/main.yml new file mode 100644 index 000000000..c3125d740 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/gitlab_project_access_token/tasks/main.yml @@ -0,0 +1,221 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Copyright (c) 2024, Zoran Krleza <zoran.krleza@true-north.hr> +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install required libs + pip: + name: python-gitlab + state: present + +- block: + - name: Try to create access token in nonexisting project + community.general.gitlab_project_access_token: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_api_url }}" + validate_certs: "{{ gitlab_validate_certs }}" + project: "some_nonexisting_project" + name: "{{ gitlab_token_name }}" + state: present + expires_at: '2025-01-01' + access_level: developer + scopes: + - api + - read_api + register: create_pfail_token_status + always: + - name: Assert that token creation in nonexisting project failed + assert: + that: + - create_pfail_token_status is failed + ignore_errors: true + +- block: + - name: Try to create access token with nonvalid expires_at + community.general.gitlab_project_access_token: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_api_url }}" + validate_certs: "{{ gitlab_validate_certs }}" + project: "some_nonexisting_project" + name: "{{ gitlab_token_name }}" + state: present + expires_at: '2025-13-01' + access_level: developer + scopes: + - api + - read_api + register: create_efail_token_status + always: + - name: Assert that token creation with invalid expires_at failed + assert: + that: + - create_efail_token_status is failed + ignore_errors: true + +- name: Create access token + community.general.gitlab_project_access_token: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_api_url }}" + validate_certs: "{{ gitlab_validate_certs }}" + project: "{{ gitlab_project_name }}" + name: "{{ gitlab_token_name }}" + state: present + expires_at: '2024-12-31' + access_level: developer + scopes: + - api + - read_api + register: create_token_status +- name: Assert that token creation with valid arguments is successfull + assert: + that: + - create_token_status is changed + - create_token_status.access_token.token is defined + +- name: Check existing access token recreate=never (default) + community.general.gitlab_project_access_token: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_api_url }}" + validate_certs: "{{ gitlab_validate_certs }}" + project: "{{ gitlab_project_name }}" + name: "{{ gitlab_token_name }}" + state: present + expires_at: '2024-12-31' + access_level: developer + scopes: + - api + - read_api + register: check_token_status +- name: Assert that token creation without changes and recreate=never succeeds with status not changed + assert: + that: + - check_token_status is not changed + - check_token_status.access_token.token is not defined + +- name: Check existing access token with recreate=state_change + community.general.gitlab_project_access_token: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_api_url }}" + validate_certs: "{{ gitlab_validate_certs }}" + project: "{{ gitlab_project_name }}" + name: "{{ gitlab_token_name }}" + state: present + expires_at: '2024-12-31' + access_level: developer + scopes: + - api + - read_api + recreate: state_change + register: check_recreate_token_status +- name: Assert that token creation without changes and recreate=state_change succeeds with status not changed + assert: + that: + - check_recreate_token_status is not changed + - check_recreate_token_status.access_token.token is not defined + +- block: + - name: Try to change existing access token with recreate=never + community.general.gitlab_project_access_token: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_api_url }}" + validate_certs: "{{ gitlab_validate_certs }}" + project: "{{ gitlab_project_name }}" + name: "{{ gitlab_token_name }}" + state: present + expires_at: '2025-01-01' + access_level: developer + scopes: + - api + - read_api + register: change_token_status + always: + - name: Assert that token change with recreate=never fails + assert: + that: + - change_token_status is failed + ignore_errors: true + +- name: Try to change existing access token with recreate=state_change + community.general.gitlab_project_access_token: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_api_url }}" + validate_certs: "{{ gitlab_validate_certs }}" + project: "{{ gitlab_project_name }}" + name: "{{ gitlab_token_name }}" + state: present + expires_at: '2025-01-01' + access_level: developer + scopes: + - api + - read_api + recreate: state_change + register: change_recreate_token_status +- name: Assert that token change with recreate=state_change succeeds + assert: + that: + - change_recreate_token_status is changed + - change_recreate_token_status.access_token.token is defined + +- name: Try to change existing access token with recreate=always + community.general.gitlab_project_access_token: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_api_url }}" + validate_certs: "{{ gitlab_validate_certs }}" + project: "{{ gitlab_project_name }}" + name: "{{ gitlab_token_name }}" + state: present + expires_at: '2025-01-01' + access_level: developer + scopes: + - api + - read_api + recreate: always + register: change_recreate1_token_status +- name: Assert that token change with recreate=always succeeds + assert: + that: + - change_recreate1_token_status is changed + - change_recreate1_token_status.access_token.token is defined + +- name: Revoke access token + community.general.gitlab_project_access_token: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_api_url }}" + validate_certs: "{{ gitlab_validate_certs }}" + project: "{{ gitlab_project_name }}" + name: "{{ gitlab_token_name }}" + state: absent + expires_at: '2024-12-31' + access_level: developer + scopes: + - api + - read_api + register: revoke_token_status +- name: Assert that token revocation succeeds + assert: + that: + - revoke_token_status is changed + +- name: Revoke nonexisting access token + community.general.gitlab_project_access_token: + api_token: "{{ gitlab_api_token }}" + api_url: "{{ gitlab_api_url }}" + validate_certs: "{{ gitlab_validate_certs }}" + project: "{{ gitlab_project_name }}" + name: "{{ gitlab_token_name }}" + state: absent + expires_at: '2024-12-31' + access_level: developer + scopes: + - api + - read_api + register: revoke_token_status +- name: Assert that token revocation succeeds with status not changed + assert: + that: + - revoke_token_status is not changed
\ No newline at end of file diff --git a/ansible_collections/community/general/tests/integration/targets/homebrew/aliases b/ansible_collections/community/general/tests/integration/targets/homebrew/aliases index 11bb9a086..bd478505d 100644 --- a/ansible_collections/community/general/tests/integration/targets/homebrew/aliases +++ b/ansible_collections/community/general/tests/integration/targets/homebrew/aliases @@ -7,4 +7,3 @@ skip/aix skip/freebsd skip/rhel skip/docker -skip/python2.6 diff --git a/ansible_collections/community/general/tests/integration/targets/homebrew/tasks/casks.yml b/ansible_collections/community/general/tests/integration/targets/homebrew/tasks/casks.yml new file mode 100644 index 000000000..42d3515bf --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/homebrew/tasks/casks.yml @@ -0,0 +1,99 @@ +--- +#################################################################### +# 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 homebrew module. +# Copyright (c) 2020, Abhijeet Kasurde <akasurde@redhat.com> +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Find brew binary + command: which brew + register: brew_which + when: ansible_distribution in ['MacOSX'] + +- name: Get owner of brew binary + stat: + path: "{{ brew_which.stdout }}" + register: brew_stat + when: ansible_distribution in ['MacOSX'] + +#- name: Use ignored-pinned option while upgrading all +# homebrew: +# upgrade_all: true +# upgrade_options: ignore-pinned +# become: true +# become_user: "{{ brew_stat.stat.pw_name }}" +# register: upgrade_option_result +# environment: +# HOMEBREW_NO_AUTO_UPDATE: True + +#- assert: +# that: +# - upgrade_option_result.changed + +- vars: + package_name: kitty + + block: + - name: Make sure {{ package_name }} package is not installed + homebrew: + name: "{{ package_name }}" + state: absent + update_homebrew: false + become: true + become_user: "{{ brew_stat.stat.pw_name }}" + + - name: Install {{ package_name }} package using homebrew + homebrew: + name: "{{ package_name }}" + state: present + update_homebrew: false + become: true + become_user: "{{ brew_stat.stat.pw_name }}" + register: package_result + + - assert: + that: + - package_result.changed + + - name: Again install {{ package_name }} package using homebrew + homebrew: + name: "{{ package_name }}" + state: present + update_homebrew: false + become: true + become_user: "{{ brew_stat.stat.pw_name }}" + register: package_result + + - assert: + that: + - not package_result.changed + + - name: Uninstall {{ package_name }} package using homebrew + homebrew: + name: "{{ package_name }}" + state: absent + update_homebrew: false + become: true + become_user: "{{ brew_stat.stat.pw_name }}" + register: package_result + + - assert: + that: + - package_result.changed + + - name: Again uninstall {{ package_name }} package using homebrew + homebrew: + name: "{{ package_name }}" + state: absent + update_homebrew: false + become: true + become_user: "{{ brew_stat.stat.pw_name }}" + register: package_result + + - assert: + that: + - not package_result.changed diff --git a/ansible_collections/community/general/tests/integration/targets/homebrew/tasks/formulae.yml b/ansible_collections/community/general/tests/integration/targets/homebrew/tasks/formulae.yml new file mode 100644 index 000000000..1db3ef1a6 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/homebrew/tasks/formulae.yml @@ -0,0 +1,99 @@ +--- +#################################################################### +# 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 homebrew module. +# Copyright (c) 2020, Abhijeet Kasurde <akasurde@redhat.com> +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Find brew binary + command: which brew + register: brew_which + when: ansible_distribution in ['MacOSX'] + +- name: Get owner of brew binary + stat: + path: "{{ brew_which.stdout }}" + register: brew_stat + when: ansible_distribution in ['MacOSX'] + +#- name: Use ignored-pinned option while upgrading all +# homebrew: +# upgrade_all: true +# upgrade_options: ignore-pinned +# become: true +# become_user: "{{ brew_stat.stat.pw_name }}" +# register: upgrade_option_result +# environment: +# HOMEBREW_NO_AUTO_UPDATE: True + +#- assert: +# that: +# - upgrade_option_result.changed + +- vars: + package_name: gnu-tar + + block: + - name: Make sure {{ package_name }} package is not installed + homebrew: + name: "{{ package_name }}" + state: absent + update_homebrew: false + become: true + become_user: "{{ brew_stat.stat.pw_name }}" + + - name: Install {{ package_name }} package using homebrew + homebrew: + name: "{{ package_name }}" + state: present + update_homebrew: false + become: true + become_user: "{{ brew_stat.stat.pw_name }}" + register: package_result + + - assert: + that: + - package_result.changed + + - name: Again install {{ package_name }} package using homebrew + homebrew: + name: "{{ package_name }}" + state: present + update_homebrew: false + become: true + become_user: "{{ brew_stat.stat.pw_name }}" + register: package_result + + - assert: + that: + - not package_result.changed + + - name: Uninstall {{ package_name }} package using homebrew + homebrew: + name: "{{ package_name }}" + state: absent + update_homebrew: false + become: true + become_user: "{{ brew_stat.stat.pw_name }}" + register: package_result + + - assert: + that: + - package_result.changed + + - name: Again uninstall {{ package_name }} package using homebrew + homebrew: + name: "{{ package_name }}" + state: absent + update_homebrew: false + become: true + become_user: "{{ brew_stat.stat.pw_name }}" + register: package_result + + - assert: + that: + - not package_result.changed diff --git a/ansible_collections/community/general/tests/integration/targets/homebrew/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/homebrew/tasks/main.yml index 1db3ef1a6..f5479917e 100644 --- a/ansible_collections/community/general/tests/integration/targets/homebrew/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/homebrew/tasks/main.yml @@ -9,91 +9,9 @@ # 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: Find brew binary - command: which brew - register: brew_which - when: ansible_distribution in ['MacOSX'] - -- name: Get owner of brew binary - stat: - path: "{{ brew_which.stdout }}" - register: brew_stat - when: ansible_distribution in ['MacOSX'] - -#- name: Use ignored-pinned option while upgrading all -# homebrew: -# upgrade_all: true -# upgrade_options: ignore-pinned -# become: true -# become_user: "{{ brew_stat.stat.pw_name }}" -# register: upgrade_option_result -# environment: -# HOMEBREW_NO_AUTO_UPDATE: True - -#- assert: -# that: -# - upgrade_option_result.changed - -- vars: - package_name: gnu-tar - +- block: + - include_tasks: 'formulae.yml' + +- when: ansible_distribution in ['MacOSX'] block: - - name: Make sure {{ package_name }} package is not installed - homebrew: - name: "{{ package_name }}" - state: absent - update_homebrew: false - become: true - become_user: "{{ brew_stat.stat.pw_name }}" - - - name: Install {{ package_name }} package using homebrew - homebrew: - name: "{{ package_name }}" - state: present - update_homebrew: false - become: true - become_user: "{{ brew_stat.stat.pw_name }}" - register: package_result - - - assert: - that: - - package_result.changed - - - name: Again install {{ package_name }} package using homebrew - homebrew: - name: "{{ package_name }}" - state: present - update_homebrew: false - become: true - become_user: "{{ brew_stat.stat.pw_name }}" - register: package_result - - - assert: - that: - - not package_result.changed - - - name: Uninstall {{ package_name }} package using homebrew - homebrew: - name: "{{ package_name }}" - state: absent - update_homebrew: false - become: true - become_user: "{{ brew_stat.stat.pw_name }}" - register: package_result - - - assert: - that: - - package_result.changed - - - name: Again uninstall {{ package_name }} package using homebrew - homebrew: - name: "{{ package_name }}" - state: absent - update_homebrew: false - become: true - become_user: "{{ brew_stat.stat.pw_name }}" - register: package_result - - - assert: - that: - - not package_result.changed + - include_tasks: 'casks.yml' diff --git a/ansible_collections/community/general/tests/integration/targets/homebrew_cask/aliases b/ansible_collections/community/general/tests/integration/targets/homebrew_cask/aliases index 11bb9a086..bd478505d 100644 --- a/ansible_collections/community/general/tests/integration/targets/homebrew_cask/aliases +++ b/ansible_collections/community/general/tests/integration/targets/homebrew_cask/aliases @@ -7,4 +7,3 @@ skip/aix skip/freebsd skip/rhel skip/docker -skip/python2.6 diff --git a/ansible_collections/community/general/tests/integration/targets/homectl/aliases b/ansible_collections/community/general/tests/integration/targets/homectl/aliases index b87db2e43..ea9b44230 100644 --- a/ansible_collections/community/general/tests/integration/targets/homectl/aliases +++ b/ansible_collections/community/general/tests/integration/targets/homectl/aliases @@ -9,3 +9,5 @@ skip/osx skip/macos skip/rhel9.0 # See https://www.reddit.com/r/Fedora/comments/si7nzk/homectl/ skip/rhel9.1 # See https://www.reddit.com/r/Fedora/comments/si7nzk/homectl/ +skip/rhel9.2 # See https://www.reddit.com/r/Fedora/comments/si7nzk/homectl/ +skip/rhel9.3 # See https://www.reddit.com/r/Fedora/comments/si7nzk/homectl/ diff --git a/ansible_collections/community/general/tests/integration/targets/htpasswd/aliases b/ansible_collections/community/general/tests/integration/targets/htpasswd/aliases new file mode 100644 index 000000000..e3339b210 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/htpasswd/aliases @@ -0,0 +1,7 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/posix/2 +destructive +needs/root diff --git a/ansible_collections/community/general/tests/integration/targets/htpasswd/handlers/main.yml b/ansible_collections/community/general/tests/integration/targets/htpasswd/handlers/main.yml new file mode 100644 index 000000000..6befa0cd3 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/htpasswd/handlers/main.yml @@ -0,0 +1,9 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: remove passlib + ansible.builtin.pip: + name: passlib + state: absent diff --git a/ansible_collections/community/general/tests/integration/targets/htpasswd/meta/main.yml b/ansible_collections/community/general/tests/integration/targets/htpasswd/meta/main.yml new file mode 100644 index 000000000..982de6eb0 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/htpasswd/meta/main.yml @@ -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 diff --git a/ansible_collections/community/general/tests/integration/targets/htpasswd/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/htpasswd/tasks/main.yml new file mode 100644 index 000000000..7b5dc3c51 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/htpasswd/tasks/main.yml @@ -0,0 +1,83 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: install passlib + ansible.builtin.pip: + name: passlib + notify: remove passlib + +- name: add bob (check mode) + community.general.htpasswd: + path: "{{ htpasswd_path }}" + name: bob + password: c00lbob + check_mode: true + register: add_bob_check + +- name: add bob + community.general.htpasswd: + path: "{{ htpasswd_path }}" + name: bob + password: c00lbob + register: add_bob + +- name: add bob (idempotency) + community.general.htpasswd: + path: "{{ htpasswd_path }}" + name: bob + password: c00lbob + register: add_bob_idempot + +- name: add bob new password + community.general.htpasswd: + path: "{{ htpasswd_path }}" + name: bob + password: SUPERsecret + register: add_bob_newpw + +- name: add bob new password (idempotency) + community.general.htpasswd: + path: "{{ htpasswd_path }}" + name: bob + password: SUPERsecret + register: add_bob_newpw_idempot + +- name: test add bob assertions + ansible.builtin.assert: + that: + - add_bob_check is changed + - add_bob is changed + - add_bob_idempot is not changed + - add_bob_newpw is changed + - add_bob_newpw_idempot is not changed + +- name: remove bob (check mode) + community.general.htpasswd: + path: "{{ htpasswd_path }}" + name: bob + state: absent + check_mode: true + register: del_bob_check + +- name: remove bob + community.general.htpasswd: + path: "{{ htpasswd_path }}" + name: bob + state: absent + register: del_bob + +- name: remove bob (idempotency) + community.general.htpasswd: + path: "{{ htpasswd_path }}" + name: bob + state: absent + register: del_bob_idempot + +- name: test remove bob assertions + ansible.builtin.assert: + that: + - del_bob_check is changed + - del_bob is changed + - del_bob_idempot is not changed diff --git a/ansible_collections/community/general/tests/integration/targets/htpasswd/vars/main.yml b/ansible_collections/community/general/tests/integration/targets/htpasswd/vars/main.yml new file mode 100644 index 000000000..ff81959c4 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/htpasswd/vars/main.yml @@ -0,0 +1,6 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +htpasswd_path: "{{ remote_tmp_dir }}/dot_htpasswd" diff --git a/ansible_collections/community/general/tests/integration/targets/ini_file/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/ini_file/tasks/main.yml index 11c5bf3b2..0ed3c2817 100644 --- a/ansible_collections/community/general/tests/integration/targets/ini_file/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/ini_file/tasks/main.yml @@ -38,3 +38,15 @@ - name: include tasks to test regressions include_tasks: tests/03-encoding.yml + + - name: include tasks to test symlink handling + include_tasks: tests/04-symlink.yml + + - name: include tasks to test ignore_spaces + include_tasks: tests/05-ignore_spaces.yml + + - name: include tasks to test modify_inactive_option + include_tasks: tests/06-modify_inactive_option.yml + + - name: include tasks to test optional spaces in section headings + include_tasks: tests/07-section_name_spaces.yml diff --git a/ansible_collections/community/general/tests/integration/targets/ini_file/tasks/tests/00-basic.yml b/ansible_collections/community/general/tests/integration/targets/ini_file/tasks/tests/00-basic.yml index c619e937a..f36fd54c5 100644 --- a/ansible_collections/community/general/tests/integration/targets/ini_file/tasks/tests/00-basic.yml +++ b/ansible_collections/community/general/tests/integration/targets/ini_file/tasks/tests/00-basic.yml @@ -3,7 +3,7 @@ # 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 -## basiscs +## basics - name: test-basic 1 - specify both "value" and "values" and fail ini_file: @@ -39,4 +39,4 @@ that: - result_basic_2 is not changed - result_basic_2 is failed - - result_basic_2.msg == "Destination {{ non_existing_file }} does not exist!" + - result_basic_2.msg == "Destination " ~ non_existing_file ~ " does not exist!" diff --git a/ansible_collections/community/general/tests/integration/targets/ini_file/tasks/tests/04-symlink.yml b/ansible_collections/community/general/tests/integration/targets/ini_file/tasks/tests/04-symlink.yml new file mode 100644 index 000000000..7e83a010d --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/ini_file/tasks/tests/04-symlink.yml @@ -0,0 +1,59 @@ +--- +# 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 + +- block: &prepare + - name: Create the final file + ansible.builtin.copy: + content: | + [main] + foo=BAR + dest: "{{ remote_tmp_dir }}/my_original_file.ini" + - name: Clean up symlink.ini + ansible.builtin.file: + path: "{{ remote_tmp_dir }}/symlink.ini" + state: absent + - name: Create a symbolic link + ansible.builtin.file: + src: my_original_file.ini + dest: "{{ remote_tmp_dir }}/symlink.ini" + state: link + +- name: Set the proxy key on the symlink which will be converted as a file + community.general.ini_file: + path: "{{ remote_tmp_dir }}/symlink.ini" + section: main + option: proxy + value: 'http://proxy.myorg.org:3128' +- name: Set the proxy key on the final file that is still unchanged + community.general.ini_file: + path: "{{ remote_tmp_dir }}/my_original_file.ini" + section: main + option: proxy + value: 'http://proxy.myorg.org:3128' + register: result +- ansible.builtin.assert: + that: + - result is changed + +# With follow +- block: *prepare +- name: Set the proxy key on the symlink which will be preserved + community.general.ini_file: + path: "{{ remote_tmp_dir }}/symlink.ini" + section: main + option: proxy + value: 'http://proxy.myorg.org:3128' + follow: true + register: result +- name: Set the proxy key on the target directly that was changed in the previous step + community.general.ini_file: + path: "{{ remote_tmp_dir }}/my_original_file.ini" + section: main + option: proxy + value: 'http://proxy.myorg.org:3128' + register: result +- ansible.builtin.assert: + that: + - "not (result is changed)" diff --git a/ansible_collections/community/general/tests/integration/targets/ini_file/tasks/tests/05-ignore_spaces.yml b/ansible_collections/community/general/tests/integration/targets/ini_file/tasks/tests/05-ignore_spaces.yml new file mode 100644 index 000000000..3c4b068fb --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/ini_file/tasks/tests/05-ignore_spaces.yml @@ -0,0 +1,123 @@ +--- +# 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 + +## testing ignore_spaces option + +- name: test-ignore_spaces 1 (commented line updated) - create test file + copy: + dest: "{{ output_file }}" + content: "[foo]\n; bar=baz\n" + +- name: test-ignore_spaces 1 - set new value + ini_file: + path: "{{ output_file }}" + section: foo + option: bar + value: frelt + ignore_spaces: true + register: result + +- name: test-ignore_spaces 1 - read content from output file + slurp: + src: "{{ output_file }}" + register: output_content + +- name: test-ignore_spaces 1 - verify results + vars: + actual_content: "{{ output_content.content | b64decode }}" + expected_content: "[foo]\nbar = frelt\n" + assert: + that: + - actual_content == expected_content + - result is changed + - result.msg == 'option changed' + +- name: test-ignore_spaces 2 (uncommented line updated) - create test file + copy: + dest: "{{ output_file }}" + content: "[foo]\nbar=baz\n" + +- name: test-ignore_spaces 2 - set new value + ini_file: + path: "{{ output_file }}" + section: foo + option: bar + value: frelt + ignore_spaces: true + register: result + +- name: test-ignore_spaces 2 - read content from output file + slurp: + src: "{{ output_file }}" + register: output_content + +- name: test-ignore_spaces 2 - verify results + vars: + actual_content: "{{ output_content.content | b64decode }}" + expected_content: "[foo]\nbar = frelt\n" + assert: + that: + - actual_content == expected_content + - result is changed + - result.msg == 'option changed' + +- name: test-ignore_spaces 3 (spaces on top of no spaces) - create test file + copy: + dest: "{{ output_file }}" + content: "[foo]\nbar=baz\n" + +- name: test-ignore_spaces 3 - try to set value + ini_file: + path: "{{ output_file }}" + section: foo + option: bar + value: baz + ignore_spaces: true + register: result + +- name: test-ignore_spaces 3 - read content from output file + slurp: + src: "{{ output_file }}" + register: output_content + +- name: test-ignore_spaces 3 - verify results + vars: + actual_content: "{{ output_content.content | b64decode }}" + expected_content: "[foo]\nbar=baz\n" + assert: + that: + - actual_content == expected_content + - result is not changed + - result.msg == "OK" + +- name: test-ignore_spaces 4 (no spaces on top of spaces) - create test file + copy: + dest: "{{ output_file }}" + content: "[foo]\nbar = baz\n" + +- name: test-ignore_spaces 4 - try to set value + ini_file: + path: "{{ output_file }}" + section: foo + option: bar + value: baz + ignore_spaces: true + no_extra_spaces: true + register: result + +- name: test-ignore_spaces 4 - read content from output file + slurp: + src: "{{ output_file }}" + register: output_content + +- name: test-ignore_spaces 4 - verify results + vars: + actual_content: "{{ output_content.content | b64decode }}" + expected_content: "[foo]\nbar = baz\n" + assert: + that: + - actual_content == expected_content + - result is not changed + - result.msg == "OK" diff --git a/ansible_collections/community/general/tests/integration/targets/ini_file/tasks/tests/06-modify_inactive_option.yml b/ansible_collections/community/general/tests/integration/targets/ini_file/tasks/tests/06-modify_inactive_option.yml new file mode 100644 index 000000000..2d1d04928 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/ini_file/tasks/tests/06-modify_inactive_option.yml @@ -0,0 +1,123 @@ +--- +# 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 + +## testing modify_inactive_option option + +- name: test-modify_inactive_option 1 - create test file + copy: + content: | + + [section1] + # Uncomment the line below to enable foo + # foo = bar + + dest: "{{ output_file }}" + +- name: test-modify_inactive_option 1 - set value for foo with modify_inactive_option set to true + ini_file: + path: "{{ output_file }}" + section: section1 + option: foo + value: bar + modify_inactive_option: true + register: result1 + +- name: test-modify_inactive_option 1 - read content from output file + slurp: + src: "{{ output_file }}" + register: output_content + +- name: test-modify_inactive_option 1 - set expected content and get current ini file content + set_fact: + expected1: | + + [section1] + # Uncomment the line below to enable foo + foo = bar + + content1: "{{ output_content.content | b64decode }}" + +- name: test-modify_inactive_option 1 - assert 'changed' is true, content is OK and option changed + assert: + that: + - result1 is changed + - result1.msg == 'option changed' + - content1 == expected1 + + +- name: test-modify_inactive_option 2 - create test file + copy: + content: | + + [section1] + # Uncomment the line below to enable foo + # foo = bar + + dest: "{{ output_file }}" + +- name: test-modify_inactive_option 2 - set value for foo with modify_inactive_option set to false + ini_file: + path: "{{ output_file }}" + section: section1 + option: foo + value: bar + modify_inactive_option: false + register: result2 + +- name: test-modify_inactive_option 2 - read content from output file + slurp: + src: "{{ output_file }}" + register: output_content + +- name: test-modify_inactive_option 2 - set expected content and get current ini file content + set_fact: + expected2: | + + [section1] + foo = bar + # Uncomment the line below to enable foo + # foo = bar + + content2: "{{ output_content.content | b64decode }}" + +- name: test-modify_inactive_option 2 - assert 'changed' is true and content is OK and option added + assert: + that: + - result2 is changed + - result2.msg == 'option added' + - content2 == expected2 + + +- name: test-modify_inactive_option 3 - remove foo=bar with modify_inactive_option set to true to ensure it doesn't have effect for removal + ini_file: + path: "{{ output_file }}" + section: section1 + option: foo + value: bar + modify_inactive_option: true + state: absent + register: result3 + +- name: test-modify_inactive_option 3 - read content from output file + slurp: + src: "{{ output_file }}" + register: output_content + +- name: test-modify_inactive_option 3 - set expected content and get current ini file content + set_fact: + expected3: | + + [section1] + # Uncomment the line below to enable foo + # foo = bar + + content3: "{{ output_content.content | b64decode }}" + +- name: test-modify_inactive_option 3 - assert 'changed' is true and content is OK and active option removed + assert: + that: + - result3 is changed + - result3.msg == 'option changed' + - content3 == expected3
\ No newline at end of file diff --git a/ansible_collections/community/general/tests/integration/targets/ini_file/tasks/tests/07-section_name_spaces.yml b/ansible_collections/community/general/tests/integration/targets/ini_file/tasks/tests/07-section_name_spaces.yml new file mode 100644 index 000000000..6cdcfef40 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/ini_file/tasks/tests/07-section_name_spaces.yml @@ -0,0 +1,103 @@ +--- +# 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 + +## testing support for optional spaces between brackets and section names + +- name: Test-section_name_spaces 1 (does legacy workaround still work) - create test file + ansible.builtin.copy: # noqa risky-file-permissions + dest: "{{ output_file }}" + content: | + [ foo ] + ; bar=baz + +- name: Test-section_name_spaces 1 - update with optional spaces specified + community.general.ini_file: # noqa risky-file-permissions + path: "{{ output_file }}" + section: ' foo ' + option: bar + value: frelt + register: result + +- name: Test-section_name_spaces 1 - read content from output file + ansible.builtin.slurp: + src: "{{ output_file }}" + register: output_content + +- name: Test-section_name_spaces 1 - verify results + vars: + actual_content: "{{ output_content.content | b64decode }}" + expected_content: | + [ foo ] + bar = frelt + ansible.builtin.assert: + that: + - actual_content == expected_content + - result is changed + - result.msg == 'option changed' + + +- name: Test-section_name_spaces 2 (optional spaces omitted) - create test file + ansible.builtin.copy: # noqa risky-file-permissions + dest: "{{ output_file }}" + content: | + [ foo ] + bar=baz" + +- name: Test-section_name_spaces 2 - update without optional spaces + community.general.ini_file: # noqa risky-file-permissions + path: "{{ output_file }}" + section: foo + option: bar + value: frelt + ignore_spaces: true + register: result + +- name: Test-section_name_spaces 2 - read content from output file + ansible.builtin.slurp: + src: "{{ output_file }}" + register: output_content + +- name: Test-section_name_spaces 2 - verify results + vars: + actual_content: "{{ output_content.content | b64decode }}" + expected_content: "[ foo ]\nbar = frelt\n" + ansible.builtin.assert: + that: + - actual_content == expected_content + - result is changed + - result.msg == 'option changed' + + +- name: Test-section_name_spaces 3 (legacy workaround when not required) - create test file + ansible.builtin.copy: # noqa risky-file-permissions + dest: "{{ output_file }}" + content: | + [foo] + ; bar=baz + +- name: Test-section_name_spaces 3 - update with optional spaces specified + community.general.ini_file: # noqa risky-file-permissions + path: "{{ output_file }}" + section: ' foo ' + option: bar + value: frelt + register: result + +- name: Test-section_name_spaces 3 - read content from output file + ansible.builtin.slurp: + src: "{{ output_file }}" + register: output_content + +- name: Test-section_name_spaces 3 - verify results + vars: + actual_content: "{{ output_content.content | b64decode }}" + expected_content: | + [foo] + bar = frelt + ansible.builtin.assert: + that: + - actual_content == expected_content + - result is changed + - result.msg == 'option changed' diff --git a/ansible_collections/community/general/tests/integration/targets/interfaces_file/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/interfaces_file/tasks/main.yml index 918a32331..18af12f5a 100644 --- a/ansible_collections/community/general/tests/integration/targets/interfaces_file/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/interfaces_file/tasks/main.yml @@ -7,6 +7,7 @@ set_fact: interfaces_testfile: '{{ remote_tmp_dir }}/interfaces' interfaces_testfile_3841: '{{ remote_tmp_dir }}/interfaces_3841' + interfaces_testfile_7610: '{{ remote_tmp_dir }}/interfaces_7610' - name: Copy interfaces file copy: @@ -65,3 +66,60 @@ that: - ifile_3841_a is changed - ifile_3841_b is not changed + +- name: 7610 - create file + copy: + dest: '{{ interfaces_testfile_7610 }}' + content: | + iface ens3 inet dhcp + iface ens3 inet6 auto + +- name: 7610 - modify file + interfaces_file: + dest: '{{ interfaces_testfile_7610 }}' + iface: ens3 + address_family: "inet6" + option: "{{ item.option }}" + value: "{{ item.value }}" + loop: + - option: "method" + value: "static" + - option: "address" + value: "1:2::3/48" + +- name: 7610 - read file + slurp: + src: '{{ interfaces_testfile_7610 }}' + register: content_7610 + +- name: 7610 - check assertions + assert: + that: + - content_7610.content | b64decode == expected_content + vars: + expected_content: | + iface ens3 inet dhcp + iface ens3 inet6 static + address 1:2::3/48 + +- name: 7610 - modify file again + interfaces_file: + dest: '{{ interfaces_testfile_7610 }}' + iface: ens3 + option: method + value: foobar + +- name: 7610 - read file + slurp: + src: '{{ interfaces_testfile_7610 }}' + register: content_7610 + +- name: 7610 - check assertions + assert: + that: + - content_7610.content | b64decode == expected_content + vars: + expected_content: | + iface ens3 inet foobar + iface ens3 inet6 foobar + address 1:2::3/48 diff --git a/ansible_collections/community/general/tests/integration/targets/iptables_state/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/iptables_state/tasks/main.yml index a74e74df4..d55007067 100644 --- a/ansible_collections/community/general/tests/integration/targets/iptables_state/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/iptables_state/tasks/main.yml @@ -29,6 +29,12 @@ when: - xtables_lock is undefined + - name: include tasks to test partial restore files + include_tasks: tests/02-partial-restore.yml + when: + - xtables_lock is undefined + + - name: include tasks to test rollbacks include_tasks: tests/10-rollback.yml when: diff --git a/ansible_collections/community/general/tests/integration/targets/iptables_state/tasks/tests/02-partial-restore.yml b/ansible_collections/community/general/tests/integration/targets/iptables_state/tasks/tests/02-partial-restore.yml new file mode 100644 index 000000000..6da4814af --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/iptables_state/tasks/tests/02-partial-restore.yml @@ -0,0 +1,66 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: "Create initial rule set to use" + copy: + dest: "{{ iptables_tests }}" + content: | + *filter + :INPUT ACCEPT [0:0] + :FORWARD ACCEPT [0:0] + :OUTPUT ACCEPT [0:0] + -A INPUT -m state --state NEW,ESTABLISHED -j ACCEPT + COMMIT + *nat + :PREROUTING ACCEPT [151:17304] + :INPUT ACCEPT [151:17304] + :OUTPUT ACCEPT [151:17304] + :POSTROUTING ACCEPT [151:17304] + -A POSTROUTING -o eth0 -j MASQUERADE + COMMIT + +- name: "Restore initial state" + iptables_state: + path: "{{ iptables_tests }}" + state: restored + async: "{{ ansible_timeout }}" + poll: 0 + +- name: "Create partial ruleset only specifying input" + copy: + dest: "{{ iptables_tests }}" + content: | + *filter + :INPUT ACCEPT [0:0] + :FORWARD ACCEPT [0:0] + :OUTPUT ACCEPT [0:0] + -A INPUT -m state --state NEW,ESTABLISHED -j ACCEPT + COMMIT + +- name: "Check restoring partial state" + iptables_state: + path: "{{ iptables_tests }}" + state: restored + check_mode: true + register: iptables_state + + +- name: "assert that no changes are detected in check mode" + assert: + that: + - iptables_state is not changed + +- name: "Restore partial state" + iptables_state: + path: "{{ iptables_tests }}" + state: restored + register: iptables_state + async: "{{ ansible_timeout }}" + poll: 0 + +- name: "assert that no changes are made" + assert: + that: + - iptables_state is not changed
\ No newline at end of file diff --git a/ansible_collections/community/general/tests/integration/targets/iso_create/aliases b/ansible_collections/community/general/tests/integration/targets/iso_create/aliases index 4fb0bec81..73bf1b2d1 100644 --- a/ansible_collections/community/general/tests/integration/targets/iso_create/aliases +++ b/ansible_collections/community/general/tests/integration/targets/iso_create/aliases @@ -5,4 +5,3 @@ azp/posix/1 destructive skip/aix -skip/python2.6 diff --git a/ansible_collections/community/general/tests/integration/targets/iso_customize/aliases b/ansible_collections/community/general/tests/integration/targets/iso_customize/aliases index 54a0f1a04..394289c08 100644 --- a/ansible_collections/community/general/tests/integration/targets/iso_customize/aliases +++ b/ansible_collections/community/general/tests/integration/targets/iso_customize/aliases @@ -8,6 +8,5 @@ destructive skip/aix skip/freebsd skip/alpine -skip/python2.6 skip/docker needs/root diff --git a/ansible_collections/community/general/tests/integration/targets/iso_customize/tasks/iso_customize_exception.yml b/ansible_collections/community/general/tests/integration/targets/iso_customize/tasks/iso_customize_exception.yml index b2130bb6b..715f2fd38 100644 --- a/ansible_collections/community/general/tests/integration/targets/iso_customize/tasks/iso_customize_exception.yml +++ b/ansible_collections/community/general/tests/integration/targets/iso_customize/tasks/iso_customize_exception.yml @@ -59,7 +59,7 @@ failed_when: customized_result.msg.find("does not exist") == -1 # Test: filenames with whitespaces -# We report error: the user should be reponsible for the it +# We report error: the user should be responsible for the it - name: "Testcase: filenames with whitespaces" community.general.iso_customize: src_iso: "{{ test_dir }}/test.iso" diff --git a/ansible_collections/community/general/tests/integration/targets/iso_extract/aliases b/ansible_collections/community/general/tests/integration/targets/iso_extract/aliases index 33041456a..5ddca1ecb 100644 --- a/ansible_collections/community/general/tests/integration/targets/iso_extract/aliases +++ b/ansible_collections/community/general/tests/integration/targets/iso_extract/aliases @@ -9,5 +9,9 @@ skip/aix skip/osx # FIXME skip/rhel9.0 # FIXME skip/rhel9.1 # FIXME +skip/rhel9.2 # FIXME +skip/rhel9.3 # FIXME skip/freebsd12.4 # FIXME skip/freebsd13.2 # FIXME +skip/freebsd13.3 # FIXME +skip/freebsd14.0 # FIXME diff --git a/ansible_collections/community/general/tests/integration/targets/java_cert/aliases b/ansible_collections/community/general/tests/integration/targets/java_cert/aliases index 573cb189b..0d8271ff7 100644 --- a/ansible_collections/community/general/tests/integration/targets/java_cert/aliases +++ b/ansible_collections/community/general/tests/integration/targets/java_cert/aliases @@ -9,3 +9,4 @@ skip/osx skip/macos skip/freebsd needs/root +skip/rhel # FIXME: keytool seems to be broken on newer RHELs diff --git a/ansible_collections/community/general/tests/integration/targets/java_cert/files/setupSSLServer.py b/ansible_collections/community/general/tests/integration/targets/java_cert/files/setupSSLServer.py index 4b0a42185..b5a333b47 100644 --- a/ansible_collections/community/general/tests/integration/targets/java_cert/files/setupSSLServer.py +++ b/ansible_collections/community/general/tests/integration/targets/java_cert/files/setupSSLServer.py @@ -18,7 +18,14 @@ except ModuleNotFoundError: from http.server import HTTPServer, SimpleHTTPRequestHandler httpd = HTTPServer(('localhost', port), SimpleHTTPRequestHandler) -httpd.socket = ssl.wrap_socket(httpd.socket, server_side=True, - certfile=os.path.join(root_dir, 'cert.pem'), - keyfile=os.path.join(root_dir, 'key.pem')) +try: + httpd.socket = ssl.wrap_socket(httpd.socket, server_side=True, + certfile=os.path.join(root_dir, 'cert.pem'), + keyfile=os.path.join(root_dir, 'key.pem')) +except AttributeError: + # Python 3.12 or newer: + context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH) + context.load_cert_chain(certfile=os.path.join(root_dir, 'cert.pem'), + keyfile=os.path.join(root_dir, 'key.pem')) + httpd.socket = context.wrap_socket(httpd.socket) httpd.handle_request() diff --git a/ansible_collections/community/general/tests/integration/targets/java_cert/tasks/state_change.yml b/ansible_collections/community/general/tests/integration/targets/java_cert/tasks/state_change.yml index e135a60a3..f2898ddc2 100644 --- a/ansible_collections/community/general/tests/integration/targets/java_cert/tasks/state_change.yml +++ b/ansible_collections/community/general/tests/integration/targets/java_cert/tasks/state_change.yml @@ -181,7 +181,7 @@ - result_x509_changed is changed - name: | - We also want to make sure that the status doesnt change if we import the same cert + We also want to make sure that the status does not change if we import the same cert community.general.java_cert: cert_alias: test_cert cert_path: "{{ test_cert2_path }}" diff --git a/ansible_collections/community/general/tests/integration/targets/java_keystore/aliases b/ansible_collections/community/general/tests/integration/targets/java_keystore/aliases index 573cb189b..0d8271ff7 100644 --- a/ansible_collections/community/general/tests/integration/targets/java_keystore/aliases +++ b/ansible_collections/community/general/tests/integration/targets/java_keystore/aliases @@ -9,3 +9,4 @@ skip/osx skip/macos skip/freebsd needs/root +skip/rhel # FIXME: keytool seems to be broken on newer RHELs diff --git a/ansible_collections/community/general/tests/integration/targets/kernel_blacklist/aliases b/ansible_collections/community/general/tests/integration/targets/kernel_blacklist/aliases index afda346c4..b85ae6419 100644 --- a/ansible_collections/community/general/tests/integration/targets/kernel_blacklist/aliases +++ b/ansible_collections/community/general/tests/integration/targets/kernel_blacklist/aliases @@ -3,3 +3,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later azp/posix/1 +skip/aix +skip/freebsd +skip/osx +skip/macos diff --git a/ansible_collections/community/general/tests/integration/targets/kernel_blacklist/handlers/main.yml b/ansible_collections/community/general/tests/integration/targets/kernel_blacklist/handlers/main.yml new file mode 100644 index 000000000..814c9c51a --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/kernel_blacklist/handlers/main.yml @@ -0,0 +1,10 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Remove modprobe.d + ansible.builtin.file: + path: /etc/modprobe.d + state: absent +
\ No newline at end of file diff --git a/ansible_collections/community/general/tests/integration/targets/kernel_blacklist/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/kernel_blacklist/tasks/main.yml index e169d5479..48cd38a93 100644 --- a/ansible_collections/community/general/tests/integration/targets/kernel_blacklist/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/kernel_blacklist/tasks/main.yml @@ -36,25 +36,37 @@ path: '{{ bl_file }}' register: stat_test_1 +- name: show bl_test_1 + ansible.builtin.debug: + var: bl_test_1_depr_msgs + vars: + bl_test_1_depr_msgs: "{{ (bl_test_1.deprecations | default([])) | map(attribute='msg') }}" + # q('ansible.builtin.subelements', bl_test_1, 'deprecations', {'skip_missing': True}) }}" + - name: assert file is unchanged assert: that: - - bl_test_1 is not changed - - bl_test_1a is not changed - - orig_stat.stat.size == stat_test_1.stat.size - - orig_stat.stat.checksum == stat_test_1.stat.checksum - - orig_stat.stat.mtime == stat_test_1.stat.mtime - - stat_test_1.stat.checksum == expected_content | checksum + - bl_test_1 is not changed + - bl_test_1a is not changed + - orig_stat.stat.size == stat_test_1.stat.size + - orig_stat.stat.checksum == stat_test_1.stat.checksum + - orig_stat.stat.mtime == stat_test_1.stat.mtime + - stat_test_1.stat.checksum == expected_content | checksum vars: expected_content: | # 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 + # SPDX{{ '' }}-License-Identifier: GPL-3.0-or-later blacklist aaaa blacklist bbbb blacklist cccc +- name: test deprecation + assert: + that: + - "'deprecations' not in bl_test_1 or (ansible_version.major == 2 and ansible_version.minor == 12)" + - name: add new item to list community.general.kernel_blacklist: blacklist_file: '{{ bl_file }}' @@ -70,13 +82,13 @@ - name: assert element is added assert: that: - - bl_test_2 is changed - - slurp_test_2.content|b64decode == content + - bl_test_2 is changed + - slurp_test_2.content|b64decode == content vars: content: | # 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 + # SPDX{{ '' }}-License-Identifier: GPL-3.0-or-later blacklist aaaa blacklist bbbb @@ -95,17 +107,49 @@ src: '{{ bl_file }}' register: slurp_test_3 -- name: assert element is added +- name: assert element is removed assert: that: - - bl_test_3 is changed - - slurp_test_3.content|b64decode == content + - bl_test_3 is changed + - slurp_test_3.content|b64decode == content vars: content: | # 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 + # SPDX{{ '' }}-License-Identifier: GPL-3.0-or-later blacklist aaaa blacklist cccc blacklist dddd + +############################################################################################################################################ +# +# Issue 7362 +# + +- name: Create /etc/modprobe.d + ansible.builtin.file: + path: /etc/modprobe.d + state: directory + mode: '0755' + owner: root + group: root + notify: Remove modprobe.d + +- name: Create cls_rsvp file + ansible.builtin.copy: + dest: /etc/modprobe.d/cls_rsvp-blacklist.conf + content: | + blacklist cls_rsvp + mode: '0644' + +- name: Block potentially affected (and unused) modules (7362) + community.general.kernel_blacklist: + name: "{{ line_item }}" + state: present + blacklist_file: "/etc/modprobe.d/{{ line_item }}-blacklist.conf" + with_items: + - cifs + - cls_rsvp + loop_control: + loop_var: line_item diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_authz_authorization_scope/readme.adoc b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_authorization_scope/readme.adoc index 1941e54ef..8e052920c 100644 --- a/ansible_collections/community/general/tests/integration/targets/keycloak_authz_authorization_scope/readme.adoc +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_authorization_scope/readme.adoc @@ -20,7 +20,7 @@ docker run --name mykeycloak -p 8080:8080 -e KC_HTTP_RELATIVE_PATH=/auth -e KEYC This test suite can run against a fresh unconfigured server instance (no preconfiguration required) and cleans up after itself (undoes all -its config changes) as long as it runs through completly. While its active +its config changes) as long as it runs through completely. While its active it changes the server configuration in the following ways: * creating, modifying and deleting some keycloak groups diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/aliases b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/aliases new file mode 100644 index 000000000..bd1f02444 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/aliases @@ -0,0 +1,5 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +unsupported diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/policy/Containerfile b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/policy/Containerfile new file mode 100644 index 000000000..3303066da --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/policy/Containerfile @@ -0,0 +1,4 @@ +FROM quay.io/keycloak/keycloak:20.0.2 + +COPY policy.jar /opt/keycloak/providers/ +RUN /opt/keycloak/bin/kc.sh build diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/policy/Containerfile.license b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/policy/Containerfile.license new file mode 100644 index 000000000..a1390a69e --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/policy/Containerfile.license @@ -0,0 +1,3 @@ +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 diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/policy/META-INF/keycloak-scripts.json b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/policy/META-INF/keycloak-scripts.json new file mode 100644 index 000000000..9370c16cb --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/policy/META-INF/keycloak-scripts.json @@ -0,0 +1,14 @@ +{ + "policies": [ + { + "name": "MyPolicy1", + "fileName": "policy-1.js", + "description": "My Policy 1" + }, + { + "name": "MyPolicy2", + "fileName": "policy-2.js", + "description": "My Policy 2" + } + ] +} diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/policy/META-INF/keycloak-scripts.json.license b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/policy/META-INF/keycloak-scripts.json.license new file mode 100644 index 000000000..a1390a69e --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/policy/META-INF/keycloak-scripts.json.license @@ -0,0 +1,3 @@ +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 diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/policy/build-policy.sh b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/policy/build-policy.sh new file mode 100755 index 000000000..eeca22f7e --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/policy/build-policy.sh @@ -0,0 +1,2 @@ +#!/bin/sh +zip -r policy.jar META-INF/keycloak-scripts.json policy-1.js policy-2.js diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/policy/build-policy.sh.license b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/policy/build-policy.sh.license new file mode 100644 index 000000000..a1390a69e --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/policy/build-policy.sh.license @@ -0,0 +1,3 @@ +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 diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/policy/policy-1.js b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/policy/policy-1.js new file mode 100644 index 000000000..fa42b0719 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/policy/policy-1.js @@ -0,0 +1 @@ +$evaluation.grant(); diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/policy/policy-1.js.license b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/policy/policy-1.js.license new file mode 100644 index 000000000..a1390a69e --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/policy/policy-1.js.license @@ -0,0 +1,3 @@ +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 diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/policy/policy-2.js b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/policy/policy-2.js new file mode 100644 index 000000000..fa42b0719 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/policy/policy-2.js @@ -0,0 +1 @@ +$evaluation.grant(); diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/policy/policy-2.js.license b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/policy/policy-2.js.license new file mode 100644 index 000000000..a1390a69e --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/policy/policy-2.js.license @@ -0,0 +1,3 @@ +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 diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/readme.adoc b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/readme.adoc new file mode 100644 index 000000000..cc014158f --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/readme.adoc @@ -0,0 +1,27 @@ +// Copyright (c) Ansible Project +// GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +// SPDX-License-Identifier: GPL-3.0-or-later + +To be able to run these integration tests a keycloak server must be +reachable under a specific url with a specific admin user and password. +The exact values expected for these parameters can be found in +'vars/main.yml' file. A vanilla Keycloak server will not be sufficient: +you will need to deploy a custom JAR file with two policies: + +* _MyPolicy1:_ policy-1.js +* _MyPolicy2:_ policy-2.js + +To create a customized Keycloak test instance running on Podman first +install the "zip" command, go to the policy subdirectory and then do + +[source,shell] +---- +./build-policy.sh +podman build --tag keycloak_authz_custom_policy_test:1.0.0 . +podman rm mykeycloak && podman run --name mykeycloak -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=password -e KC_HTTP_RELATIVE_PATH=/auth localhost/keycloak_authz_custom_policy_test:1.0.0 start-dev +---- + +This process probably also work with Docker just by replacing _podman_ with +_docker_. Modify the FROM argument in Containerfile to change Keycloak version +to test against. Quarkus versions of Keycloak should work - older versions +will not. diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/tasks/main.yml new file mode 100644 index 000000000..b22d75121 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/tasks/main.yml @@ -0,0 +1,168 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later +- name: Remove keycloak client to avoid failures from previous failed runs + community.general.keycloak_client: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_id }}" + state: absent + +- name: Create keycloak client with authorization services enabled + community.general.keycloak_client: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_id }}" + state: present + enabled: true + public_client: false + service_accounts_enabled: true + authorization_services_enabled: true + +- name: Create first custom policy (check_mode) + community.general.keycloak_authz_custom_policy: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: FirstCustomPolicy + state: present + policy_type: script-policy-1.js + realm: "{{ realm }}" + client_id: "{{ client_id }}" + check_mode: true + register: result + +- name: Assert that first custom policy was not created + assert: + that: + - result is changed + - result.end_state != {} + - result.end_state.name == "FirstCustomPolicy" + - result.end_state.type == "script-policy-1.js" + - result.msg == 'Would create custom policy FirstCustomPolicy' + +- name: Create first custom policy + community.general.keycloak_authz_custom_policy: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: FirstCustomPolicy + state: present + policy_type: script-policy-1.js + realm: "{{ realm }}" + client_id: "{{ client_id }}" + register: result + +- name: Assert that first custom policy was created + assert: + that: + - result is changed + - result.end_state != {} + - result.end_state.name == "FirstCustomPolicy" + - result.end_state.type == "script-policy-1.js" + - result.msg == 'Custom policy FirstCustomPolicy created' + +- name: Attempt to update first custom policy (not possible) + community.general.keycloak_authz_custom_policy: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: FirstCustomPolicy + state: present + policy_type: script-policy-2.js + realm: "{{ realm }}" + client_id: "{{ client_id }}" + register: result + +- name: Assert that first custom policy was not modified + assert: + that: + - result is not changed + - result.end_state != {} + - result.end_state.name == "FirstCustomPolicy" + - result.end_state.type == "script-policy-2.js" + - result.msg == 'Custom policy FirstCustomPolicy already exists' + +# Ensure that we can create multiple instances of the custom policy +- name: Create second instance of the custom policy + community.general.keycloak_authz_custom_policy: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: SecondCustomPolicy + state: present + policy_type: script-policy-1.js + realm: "{{ realm }}" + client_id: "{{ client_id }}" + register: result + +- name: Assert that second instance of the custom policy was created + assert: + that: + - result is changed + - result.end_state != {} + - result.end_state.name == "SecondCustomPolicy" + - result.end_state.type == "script-policy-1.js" + - result.msg == 'Custom policy SecondCustomPolicy created' + +- name: Remove second instance of the custom policy (check_mode) + community.general.keycloak_authz_custom_policy: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: SecondCustomPolicy + state: absent + policy_type: script-policy-1.js + realm: "{{ realm }}" + client_id: "{{ client_id }}" + check_mode: true + register: result + +- name: Assert that second custom policy was not removed + assert: + that: + - result is changed + - result.end_state == {} + - result.msg == 'Would remove custom policy SecondCustomPolicy' + +- name: Remove second instance of the custom policy + community.general.keycloak_authz_custom_policy: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: SecondCustomPolicy + state: absent + policy_type: script-policy-1.js + realm: "{{ realm }}" + client_id: "{{ client_id }}" + register: result + +- name: Assert that second custom policy was removed + assert: + that: + - result is changed + - result.end_state == {} + - result.msg == 'Custom policy SecondCustomPolicy removed' + +- name: Remove keycloak client + community.general.keycloak_client: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_id }}" + state: absent diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/vars/main.yml b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/vars/main.yml new file mode 100644 index 000000000..c1d5fc983 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_custom_policy/vars/main.yml @@ -0,0 +1,11 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +url: http://localhost:8080/auth +admin_realm: master +admin_user: admin +admin_password: password +realm: master +client_id: authz diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_authz_permission/aliases b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_permission/aliases new file mode 100644 index 000000000..e1f8d6b4b --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_permission/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +unsupported +keycloak_authz_permission_info diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_authz_permission/readme.adoc b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_permission/readme.adoc new file mode 100644 index 000000000..8e052920c --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_permission/readme.adoc @@ -0,0 +1,27 @@ +// Copyright (c) Ansible Project +// GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +// SPDX-License-Identifier: GPL-3.0-or-later + +To be able to run these integration tests a keycloak server must be +reachable under a specific url with a specific admin user and password. +The exact values expected for these parameters can be found in +'vars/main.yml' file. A simple way to do this is to use the official +keycloak docker images like this: + +---- +docker run --name mykeycloak -p 8080:8080 -e KC_HTTP_RELATIVE_PATH=<url-path> -e KEYCLOAK_ADMIN=<admin_user> -e KEYCLOAK_ADMIN_PASSWORD=<admin_password> quay.io/keycloak/keycloak:20.0.2 start-dev +---- + +Example with concrete values inserted: + +---- +docker run --name mykeycloak -p 8080:8080 -e KC_HTTP_RELATIVE_PATH=/auth -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=password quay.io/keycloak/keycloak:20.0.2 start-dev +---- + +This test suite can run against a fresh unconfigured server instance +(no preconfiguration required) and cleans up after itself (undoes all +its config changes) as long as it runs through completely. While its active +it changes the server configuration in the following ways: + + * creating, modifying and deleting some keycloak groups + diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_authz_permission/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_permission/tasks/main.yml new file mode 100644 index 000000000..16cb6806f --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_permission/tasks/main.yml @@ -0,0 +1,567 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later +- name: Remove keycloak client to avoid failures from previous failed runs + community.general.keycloak_client: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_id }}" + state: absent + +- name: Create keycloak client with authorization services enabled + community.general.keycloak_client: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_id }}" + state: present + enabled: true + public_client: false + service_accounts_enabled: true + authorization_services_enabled: true + +- name: Create file:create authorization scope + community.general.keycloak_authz_authorization_scope: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + state: present + name: "file:create" + display_name: "File create" + icon_uri: "http://localhost/icon.png" + client_id: "{{ client_id }}" + realm: "{{ realm }}" + register: result + +- name: Create file:delete authorization scope + community.general.keycloak_authz_authorization_scope: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + state: present + name: "file:delete" + display_name: "File delete" + icon_uri: "http://localhost/icon.png" + client_id: "{{ client_id }}" + realm: "{{ realm }}" + register: result + +- name: Create permission without type (test for failure) + community.general.keycloak_authz_permission: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + state: present + name: "ScopePermission" + description: "Scope permission" + client_id: "{{ client_id }}" + realm: "{{ realm }}" + register: result + failed_when: result.msg.find('missing required arguments') == -1 + +- name: Create scope permission without scopes (test for failure) + community.general.keycloak_authz_permission: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + state: present + name: "ScopePermission" + description: "Scope permission" + permission_type: scope + client_id: "{{ client_id }}" + realm: "{{ realm }}" + register: result + failed_when: result.msg.find('Scopes need to defined when permission type is set to scope!') == -1 + +- name: Create scope permission with multiple resources (test for failure) + community.general.keycloak_authz_permission: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + state: present + name: "ScopePermission" + description: "Scope permission" + resources: + - "Default Resource" + - "Other Resource" + permission_type: scope + scopes: + - "file:delete" + client_id: "{{ client_id }}" + realm: "{{ realm }}" + register: result + failed_when: result.msg.find('Only one resource can be defined for a scope permission!') == -1 + +- name: Create scope permission with invalid policy name (test for failure) + community.general.keycloak_authz_permission: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + state: present + name: "ScopePermission" + description: "Scope permission" + permission_type: scope + scopes: + - "file:delete" + policies: + - "Missing Policy" + client_id: "{{ client_id }}" + realm: "{{ realm }}" + register: result + failed_when: result.msg.find('Unable to find authorization policy with name') == -1 + +- name: Create scope permission + community.general.keycloak_authz_permission: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + state: present + name: "ScopePermission" + description: "Scope permission" + permission_type: scope + scopes: + - "file:delete" + policies: + - "Default Policy" + client_id: "{{ client_id }}" + realm: "{{ realm }}" + register: result + +- name: Assert that scope permission was created + assert: + that: + - result is changed + - result.end_state != {} + - result.end_state.name == "ScopePermission" + - result.end_state.description == "Scope permission" + - result.end_state.type == "scope" + - result.end_state.resources == [] + - result.end_state.policies|length == 1 + - result.end_state.scopes|length == 1 + +- name: Query state + community.general.keycloak_authz_permission_info: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "ScopePermission" + client_id: "{{ client_id }}" + realm: "{{ realm }}" + register: result + +- name: Assert that queried state matches desired end state + assert: + that: + - result.queried_state.name == "ScopePermission" + - result.queried_state.description == "Scope permission" + +- name: Create scope permission (test for idempotency) + community.general.keycloak_authz_permission: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + state: present + name: "ScopePermission" + description: "Scope permission" + permission_type: scope + scopes: + - "file:delete" + policies: + - "Default Policy" + client_id: "{{ client_id }}" + realm: "{{ realm }}" + register: result + +- name: Assert that nothing changed + assert: + that: + - result.end_state != {} + - result.end_state.name == "ScopePermission" + - result.end_state.description == "Scope permission" + - result.end_state.type == "scope" + - result.end_state.resources == [] + - result.end_state.policies|length == 1 + - result.end_state.scopes|length == 1 + +- name: Query state + community.general.keycloak_authz_permission_info: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "ScopePermission" + client_id: "{{ client_id }}" + realm: "{{ realm }}" + register: result + +- name: Assert that queried state matches desired end state + assert: + that: + - result.queried_state.name == "ScopePermission" + - result.queried_state.description == "Scope permission" + +- name: Update scope permission + community.general.keycloak_authz_permission: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + state: present + name: "ScopePermission" + description: "Scope permission changed" + permission_type: scope + decision_strategy: 'AFFIRMATIVE' + scopes: + - "file:create" + - "file:delete" + policies: [] + client_id: "{{ client_id }}" + realm: "{{ realm }}" + register: result + +- name: Assert that scope permission was updated correctly + assert: + that: + - result.changed == True + - result.end_state != {} + - result.end_state.scopes|length == 2 + - result.end_state.policies == [] + - result.end_state.resources == [] + - result.end_state.name == "ScopePermission" + - result.end_state.description == "Scope permission changed" + +- name: Query state + community.general.keycloak_authz_permission_info: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "ScopePermission" + client_id: "{{ client_id }}" + realm: "{{ realm }}" + register: result + +- name: Assert that queried state matches desired end state + assert: + that: + - result.queried_state.name == "ScopePermission" + - result.queried_state.description == "Scope permission changed" + +- name: Update scope permission (test for idempotency) + community.general.keycloak_authz_permission: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + state: present + name: "ScopePermission" + description: "Scope permission changed" + permission_type: scope + decision_strategy: 'AFFIRMATIVE' + scopes: + - "file:create" + - "file:delete" + policies: [] + client_id: "{{ client_id }}" + realm: "{{ realm }}" + register: result + +- name: Assert that nothing changed + assert: + that: + - result.changed == True + - result.end_state != {} + - result.end_state.scopes|length == 2 + - result.end_state.policies == [] + - result.end_state.resources == [] + - result.end_state.name == "ScopePermission" + - result.end_state.description == "Scope permission changed" + +- name: Query state + community.general.keycloak_authz_permission_info: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "ScopePermission" + client_id: "{{ client_id }}" + realm: "{{ realm }}" + register: result + +- name: Assert that queried state matches desired end state + assert: + that: + - result.queried_state.name == "ScopePermission" + - result.queried_state.description == "Scope permission changed" + +- name: Remove scope permission + community.general.keycloak_authz_permission: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + state: absent + name: "ScopePermission" + permission_type: scope + client_id: "{{ client_id }}" + realm: "{{ realm }}" + register: result + +- name: Assert that scope permission was removed + assert: + that: + - result is changed + - result.end_state == {} + +- name: Remove scope permission (test for idempotency) + community.general.keycloak_authz_permission: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + state: absent + name: "ScopePermission" + permission_type: scope + client_id: "{{ client_id }}" + realm: "{{ realm }}" + register: result + +- name: Assert that nothing has changed + assert: + that: + - result is not changed + - result.end_state == {} + +- name: Create resource permission without resources (test for failure) + community.general.keycloak_authz_permission: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + state: present + name: "ResourcePermission" + description: "Resource permission" + permission_type: resource + policies: + - "Default Policy" + client_id: "{{ client_id }}" + realm: "{{ realm }}" + register: result + failed_when: result.msg.find('A resource need to defined when permission type is set to resource!') == -1 + +- name: Create resource permission with scopes (test for failure) + community.general.keycloak_authz_permission: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + state: present + name: "ResourcePermission" + description: "Resource permission" + permission_type: resource + resources: + - "Default Resource" + policies: + - "Default Policy" + scopes: + - "file:delete" + client_id: "{{ client_id }}" + realm: "{{ realm }}" + register: result + failed_when: result.msg.find('Scopes cannot be defined when permission type is set to resource!') == -1 + +- name: Create resource permission + community.general.keycloak_authz_permission: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + state: present + name: "ResourcePermission" + description: "Resource permission" + resources: + - "Default Resource" + permission_type: resource + policies: + - "Default Policy" + client_id: "{{ client_id }}" + realm: "{{ realm }}" + register: result + +- name: Assert that resource permission was created + assert: + that: + - result is changed + - result.end_state != {} + - result.end_state.policies|length == 1 + - result.end_state.resources|length == 1 + - result.end_state.name == "ResourcePermission" + - result.end_state.description == "Resource permission" + +- name: Query state + community.general.keycloak_authz_permission_info: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "ResourcePermission" + client_id: "{{ client_id }}" + realm: "{{ realm }}" + register: result + +- name: Assert that queried state matches desired end state + assert: + that: + - result.queried_state.name == "ResourcePermission" + - result.queried_state.description == "Resource permission" + +- name: Create resource permission (test for idempotency) + community.general.keycloak_authz_permission: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + state: present + name: "ResourcePermission" + description: "Resource permission" + resources: + - "Default Resource" + permission_type: resource + policies: + - "Default Policy" + client_id: "{{ client_id }}" + realm: "{{ realm }}" + register: result + +- name: Assert that nothing has changed + assert: + that: + - result.end_state != {} + - result.end_state.policies|length == 1 + - result.end_state.resources|length == 1 + - result.end_state.name == "ResourcePermission" + - result.end_state.description == "Resource permission" + +- name: Query state + community.general.keycloak_authz_permission_info: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "ResourcePermission" + client_id: "{{ client_id }}" + realm: "{{ realm }}" + register: result + +- name: Assert that queried state matches desired end state + assert: + that: + - result.queried_state.name == "ResourcePermission" + - result.queried_state.description == "Resource permission" + +- name: Update resource permission + community.general.keycloak_authz_permission: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + state: present + name: "ResourcePermission" + description: "Resource permission changed" + resources: + - "Default Resource" + permission_type: resource + policies: [] + client_id: "{{ client_id }}" + realm: "{{ realm }}" + register: result + +- name: Assert that resource permission was updated correctly + assert: + that: + - result.changed == True + - result.end_state != {} + - result.end_state.policies == [] + - result.end_state.resources|length == 1 + - result.end_state.name == "ResourcePermission" + - result.end_state.description == "Resource permission changed" + +- name: Query state + community.general.keycloak_authz_permission_info: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "ResourcePermission" + client_id: "{{ client_id }}" + realm: "{{ realm }}" + register: result + +- name: Assert that queried state matches desired end state + assert: + that: + - result.queried_state.name == "ResourcePermission" + - result.queried_state.description == "Resource permission changed" + +- name: Remove resource permission + community.general.keycloak_authz_permission: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + state: absent + name: "ResourcePermission" + permission_type: resource + client_id: "{{ client_id }}" + realm: "{{ realm }}" + register: result + +- name: Assert that resource permission was removed + assert: + that: + - result is changed + - result.end_state == {} + +- name: Remove resource permission (test for idempotency) + community.general.keycloak_authz_permission: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + state: absent + name: "ResourcePermission" + permission_type: resource + client_id: "{{ client_id }}" + realm: "{{ realm }}" + register: result + +- name: Assert that nothing has changed + assert: + that: + - result is not changed + - result.end_state == {} + +- name: Remove keycloak client + community.general.keycloak_client: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_id }}" + state: absent diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_authz_permission/vars/main.yml b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_permission/vars/main.yml new file mode 100644 index 000000000..c1d5fc983 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_authz_permission/vars/main.yml @@ -0,0 +1,11 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +url: http://localhost:8080/auth +admin_realm: master +admin_user: admin +admin_password: password +realm: master +client_id: authz diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_client/README.md b/ansible_collections/community/general/tests/integration/targets/keycloak_client/README.md index d8bcc08ec..f2b1012aa 100644 --- a/ansible_collections/community/general/tests/integration/targets/keycloak_client/README.md +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_client/README.md @@ -4,14 +4,16 @@ GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://w SPDX-License-Identifier: GPL-3.0-or-later --> -The integration test can be performed as follows: +# Running keycloak_client module integration test -``` -# 1. Start docker-compose: -docker-compose -f tests/integration/targets/keycloak_client/docker-compose.yml stop -docker-compose -f tests/integration/targets/keycloak_client/docker-compose.yml rm -f -v -docker-compose -f tests/integration/targets/keycloak_client/docker-compose.yml up -d +To run Keycloak client module's integration test, start a keycloak server using Docker: -# 2. Run the integration tests: -ansible-test integration keycloak_client --allow-unsupported -v -``` + docker run -d --rm --name mykeycloak -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=password quay.io/keycloak/keycloak:latest start-dev --http-relative-path /auth + +Run the integration tests: + + ansible-test integration -v keycloak_client --allow-unsupported --docker fedora35 --docker-network host + +Cleanup: + + docker stop mykeycloak diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_client/docker-compose.yml b/ansible_collections/community/general/tests/integration/targets/keycloak_client/docker-compose.yml deleted file mode 100644 index 5e14e9aac..000000000 --- a/ansible_collections/community/general/tests/integration/targets/keycloak_client/docker-compose.yml +++ /dev/null @@ -1,31 +0,0 @@ ---- -# Copyright (c) Ansible Project -# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) -# SPDX-License-Identifier: GPL-3.0-or-later - -version: '3.4' - -services: - postgres: - image: postgres:9.6 - restart: always - environment: - POSTGRES_USER: postgres - POSTGRES_DB: postgres - POSTGRES_PASSWORD: postgres - - keycloak: - image: jboss/keycloak:12.0.4 - ports: - - 8080:8080 - - environment: - DB_VENDOR: postgres - DB_ADDR: postgres - DB_DATABASE: postgres - DB_USER: postgres - DB_SCHEMA: public - DB_PASSWORD: postgres - - KEYCLOAK_USER: admin - KEYCLOAK_PASSWORD: password diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_client/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/keycloak_client/tasks/main.yml index 513d5836b..5e7c7fae3 100644 --- a/ansible_collections/community/general/tests/integration/targets/keycloak_client/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_client/tasks/main.yml @@ -2,58 +2,78 @@ # 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: Wait for Keycloak + uri: + url: "{{ url }}/admin/" + status_code: 200 + validate_certs: no + register: result + until: result.status == 200 + retries: 10 + delay: 10 - name: Delete realm - community.general.keycloak_realm: "{{ auth_args | combine(call_args) }}" - vars: - call_args: - id: "{{ realm }}" - realm: "{{ realm }}" - state: absent + community.general.keycloak_realm: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + id: "{{ realm }}" + realm: "{{ realm }}" + state: absent - name: Create realm - community.general.keycloak_realm: "{{ auth_args | combine(call_args) }}" - vars: - call_args: - id: "{{ realm }}" - realm: "{{ realm }}" - state: present + community.general.keycloak_realm: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + id: "{{ realm }}" + realm: "{{ realm }}" + state: present - name: Desire client - community.general.keycloak_client: "{{ auth_args | combine(call_args) }}" - vars: - call_args: - realm: "{{ realm }}" - client_id: "{{ client_id }}" - state: present - redirect_uris: '{{redirect_uris1}}' - attributes: '{{client_attributes1}}' - protocol_mappers: '{{protocol_mappers1}}' + community.general.keycloak_client: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_id }}" + state: present + redirect_uris: '{{redirect_uris1}}' + attributes: '{{client_attributes1}}' + protocol_mappers: '{{protocol_mappers1}}' register: desire_client_not_present - name: Desire client again with same props - community.general.keycloak_client: "{{ auth_args | combine(call_args) }}" - vars: - call_args: - realm: "{{ realm }}" - client_id: "{{ client_id }}" - state: present - redirect_uris: '{{redirect_uris1}}' - attributes: '{{client_attributes1}}' - protocol_mappers: '{{protocol_mappers1}}' + community.general.keycloak_client: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_id }}" + state: present + redirect_uris: '{{redirect_uris1}}' + attributes: '{{client_attributes1}}' + protocol_mappers: '{{protocol_mappers1}}' register: desire_client_when_present_and_same - name: Check client again with same props - community.general.keycloak_client: "{{ auth_args | combine(call_args) }}" + community.general.keycloak_client: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_id }}" + state: present + redirect_uris: '{{redirect_uris1}}' + attributes: '{{client_attributes1}}' + protocol_mappers: '{{protocol_mappers1}}' + authorization_services_enabled: False check_mode: true - vars: - call_args: - realm: "{{ realm }}" - client_id: "{{ client_id }}" - state: present - redirect_uris: '{{redirect_uris1}}' - attributes: '{{client_attributes1}}' - protocol_mappers: '{{protocol_mappers1}}' register: check_client_when_present_and_same - name: Assert changes not detected in last two tasks (desire when same, and check) @@ -61,3 +81,25 @@ that: - desire_client_when_present_and_same is not changed - check_client_when_present_and_same is not changed + +- name: Check client again with changed props + community.general.keycloak_client: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_id }}" + state: present + redirect_uris: '{{redirect_uris1}}' + attributes: '{{client_attributes1}}' + protocol_mappers: '{{protocol_mappers1}}' + authorization_services_enabled: False + service_accounts_enabled: True + check_mode: true + register: check_client_when_present_and_changed + +- name: Assert changes detected in last tasks + assert: + that: + - check_client_when_present_and_changed is changed diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_client/vars/main.yml b/ansible_collections/community/general/tests/integration/targets/keycloak_client/vars/main.yml index 53ba35fca..498f93e70 100644 --- a/ansible_collections/community/general/tests/integration/targets/keycloak_client/vars/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_client/vars/main.yml @@ -24,7 +24,7 @@ redirect_uris1: - "http://example.b.com/" - "http://example.a.com/" -client_attributes1: {"backchannel.logout.session.required": true, "backchannel.logout.revoke.offline.tokens": false} +client_attributes1: {"backchannel.logout.session.required": true, "backchannel.logout.revoke.offline.tokens": false, "client.secret.creation.time": 0} protocol_mappers1: - name: 'email' diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_component_info/README.md b/ansible_collections/community/general/tests/integration/targets/keycloak_component_info/README.md new file mode 100644 index 000000000..cf4f222b0 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_component_info/README.md @@ -0,0 +1,20 @@ +<!-- +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 +--> +# Running keycloak_component_info module integration test + +To run Keycloak component info module's integration test, start a keycloak server using Docker: + + docker run -d --rm --name myldap -p 389:389 minkwe/389ds:latest + docker run -d --rm --name mykeycloak --link myldap:ldap.example.com -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=password quay.io/keycloak/keycloak:latest start-dev --http-relative-path /auth + +Run integration tests: + ansible-test integration -v keycloak_component_info --allow-unsupported --docker fedora35 --docker-network host + +Cleanup: + + docker stop myldap mykeycloak + + diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_component_info/aliases b/ansible_collections/community/general/tests/integration/targets/keycloak_component_info/aliases new file mode 100644 index 000000000..bd1f02444 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_component_info/aliases @@ -0,0 +1,5 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +unsupported diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_component_info/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/keycloak_component_info/tasks/main.yml new file mode 100644 index 000000000..c0ca5600f --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_component_info/tasks/main.yml @@ -0,0 +1,266 @@ +--- +# 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: Wait for Keycloak + uri: + url: "{{ url }}/admin/" + status_code: 200 + validate_certs: no + register: result + until: result.status == 200 + retries: 10 + delay: 10 + +- name: Delete realm if exists + community.general.keycloak_realm: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + state: absent + +- name: Create realm + community.general.keycloak_realm: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + id: "{{ realm }}" + realm: "{{ realm }}" + state: present + +- name: Retrive ldap info when absent + community.general.keycloak_component_info: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "{{ federation }}" + provider_type: "org.keycloak.storage.UserStorageProvider" + realm: "{{ realm }}" + register: result + +- name: Assert ldap is missing + assert: + that: + - result is not changed + - result.components | length == 0 + +- name: Create new user federation + community.general.keycloak_user_federation: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + name: "{{ federation }}" + state: present + provider_id: ldap + provider_type: org.keycloak.storage.UserStorageProvider + config: + enabled: true + priority: 0 + fullSyncPeriod: -1 + changedSyncPeriod: -1 + cachePolicy: DEFAULT + batchSizeForSync: 1000 + editMode: READ_ONLY + importEnabled: true + syncRegistrations: false + vendor: other + usernameLDAPAttribute: uid + rdnLDAPAttribute: uid + uuidLDAPAttribute: entryUUID + userObjectClasses: "inetOrgPerson, organizationalPerson" + connectionUrl: "ldap://ldap.example.com" + usersDn: "ou=Users,dc=example,dc=com" + authType: simple + bindDn: cn=directory reader + bindCredential: secret + searchScope: 1 + validatePasswordPolicy: false + trustEmail: false + useTruststoreSpi: "ldapsOnly" + connectionPooling: true + pagination: true + allowKerberosAuthentication: false + useKerberosForPasswordAuthentication: false + debug: false + +- name: Retrive ldap info + community.general.keycloak_component_info: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "{{ federation }}" + provider_type: "org.keycloak.storage.UserStorageProvider" + realm: "{{ realm }}" + register: result + +- name: Assert ldap exists + assert: + that: + - result is not changed + - result.components | length == 1 + - result.components[0].name == federation + +- name: Save ldap id + set_fact: + myLdapId: "{{ result.components[0].id }}" + +- name: Retrive ldap subcomponents info + community.general.keycloak_component_info: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + parent_id: "{{ myLdapId }}" + register: result + +- name: Assert components exists + assert: + that: + - result is not changed + - result.components | length > 0 + +- name: Retrive ldap subcomponents filter by name + community.general.keycloak_component_info: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + parent_id: "{{ myLdapId }}" + name: "email" + register: result + +- name: Assert sub component with name "email" exists + assert: + that: + - result is not changed + - result.components | length == 1 + - result.components[0].name == "email" + +- name: Retrive ldap subcomponents filter by type + community.general.keycloak_component_info: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + parent_id: "{{ myLdapId }}" + provider_type: "org.keycloak.storage.ldap.mappers.LDAPStorageMapper" + register: result + +- name: Assert ldap sub components filter by type + assert: + that: + - result is not changed + - result.components | length > 0 + - result.components[0].providerType == "org.keycloak.storage.ldap.mappers.LDAPStorageMapper" + +- name: Retrive key info when absent + community.general.keycloak_component_info: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "{{ realm_key_name }}" + provider_type: "org.keycloak.keys.KeyProvider" + realm: "{{ realm }}" + register: result + +- name: Assert key is missing + assert: + that: + - result is not changed + - result.components | length == 0 + +- name: Create custom realm key + community.general.keycloak_realm_key: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "{{ realm_key_name }}" + state: present + parent_id: "{{ realm }}" + config: + private_key: "{{ realm_private_key }}" + certificate: "" + enabled: true + active: true + priority: 150 + register: result + +- name: Retrive key info + community.general.keycloak_component_info: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "{{ realm_key_name }}" + provider_type: "org.keycloak.keys.KeyProvider" + realm: "{{ realm }}" + register: result + +- name: Assert key exists + assert: + that: + - result is not changed + - result.components | length == 1 + +- name: Retrive all realm components + community.general.keycloak_component_info: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + register: result + +- name: Assert key exists + assert: + that: + - result is not changed + - result.components | length > 0 + +- name: Retrive all ldap in realm + community.general.keycloak_component_info: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + provider_type: "org.keycloak.storage.UserStorageProvider" + register: result + +- name: Assert key exists + assert: + that: + - result is not changed + - result.components | length == 1 + - result.components[0].providerType == "org.keycloak.storage.UserStorageProvider" + - result.components[0].name == "myldap" + +- name: Retrive component by name only + community.general.keycloak_component_info: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + name: "{{ realm_key_name }}" + register: result + +- name: Assert key exists + assert: + that: + - result is not changed + - result.components | length == 1 + - result.components[0].providerType == "org.keycloak.keys.KeyProvider" + - result.components[0].name == realm_key_name diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_component_info/vars/main.yml b/ansible_collections/community/general/tests/integration/targets/keycloak_component_info/vars/main.yml new file mode 100644 index 000000000..7f18d8459 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_component_info/vars/main.yml @@ -0,0 +1,19 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +url: http://localhost:8080/auth +admin_realm: master +admin_user: admin +admin_password: password +realm: myrealm + +federation: myldap + + +realm_key_name: testkey +realm_private_key: | + -----BEGIN PRIVATE KEY----- + MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC9Mi7IKXPhqGiWGwgEYEXnqc8nytG1pHbC6QYZe1gUa43jDtGYQln36It02BGw4e5XydCUj+M26X2sH+kKaV+KHEnJtcEqdAuVX1QaDVzeiOoo1/B9HC8By6NZBsOSdxpat3EvilQ+R7NP9yi53J08+vfeZSEGyPmKV1me7nJnRh3/zcRsOi92GTsBd7gApKfP8sorDjY8m9NRuPLwleK2nh/aRvj1yK8x3UAqUIbOCVaE39bSN6VUTFK2Q/+MX3vF0Zugsk7PKKmfqcEW6wj7dtSElbX4uhrfTkGMmwIWdIiLDNRA/jVRvGxUB1SyMy6kmMC8jC2QGWpZgfkSKtHlAgMBAAECggEACWkSVh7ntmjtwM+Z47vVJkt2NBS8vxPt206DYOeXbzaVUV6mkrP0LSZKL3bi1GE8fW3am9UXWF8fQt04dm3c1G4JRojtkXrBq72Y3Y3eGWyGdx8chWCOPwDdwFsbhbC6ZRo8PUDcZVekJd1Vj38XbBXQl+WAUcnTzauAF+1kz9mhJq1gpglIbB+8l7VjMXwXeaGWJQ5OL/MSsq7r3P1elVjHwprFBM7HHA5+RTu/KY/GcEutgm5uwTRqRZNC1IBXAQtBO7HQJbuLqDPTQ3RRCPEur8R+0dk5bF+8IyzQ8Bh+Dhuou9xzfS/A7lV6L/CZSpv4Bvq1H3Uxk+orXf2Q2QKBgQDBOf1nSJB0VgQdIcdtgVpVgQ2SyWAd+N8Qk7QsyVQf9f7ZqiFLejWJbaaeY9WtfZ01D8tgHJfPqsO1/Jux255mtkyk2K2c6dav1Lsd4l+iPfidsDJNWkcd59nQqwC9BLjzWK/J4rO20apm34abLaZ9oVk8Mgz8VWJWOxTgCr+COQKBgQD6qP1lm6rzlCSIEz9eCuGPkQkVo+NIP437e3i+sxtkLlMgnmfzSwSJdVF8AKH3gXi3NyWjfBVYeAZEkm1kHF8IWOiK4U1y95Vx3uud3NX4SC+cjePc+pDPQJiz9L+zq9I6WFZWmm7n/9heTxu/l0vxI4FHaBmt95BMwLJNkzbdDQKBgCHGwUUMqjOr1YxCG1pJAkFwDa9bBDI5DsUXDKfHia0Mkz/5PVi0RCeBw15slS1+h7x+xk5GsULb1to5Df5JJadOtpcaST7koWKbDRpsN8tkidEGu8RJw6S2opyXR8nCyZHALvpbZo7Ol7rj1+PIVxIe4jpjhWGWi1oHed6wAkoBAoGAJx2F5XxEUhx1EvMF+XPzPQciBsl7Z0PbsTnUXtXuWVTNThLKH/I99AFlxNcIb2o530VwzzFG13Zra/n5rhyrS88sArgj8OPn40wpMopKraL+Iw0VWN+VB3KKIdL4s14FwWsVlhAlbHjFV/o6V0yR4kBrJSx+jWJLl16etHJbpmUCgYBUWCQwcT1aw9XHWJXiNYTnQSYg88hgGYhts1qSzhfu+n1t2BlAlxM0gu2+gez21mM8uiYsqbU2OZeG2U4as6kdai8Q4tzNQt2f1r3ZewJN/QHrkx6FT94PNa0w4ILiQ9Eu7xssaHcYjHyrI1NlbMKypVy6waDG2ajLOFAVeHGpOg== + -----END PRIVATE KEY----- diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_group/readme.adoc b/ansible_collections/community/general/tests/integration/targets/keycloak_group/readme.adoc index 1941e54ef..8e052920c 100644 --- a/ansible_collections/community/general/tests/integration/targets/keycloak_group/readme.adoc +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_group/readme.adoc @@ -20,7 +20,7 @@ docker run --name mykeycloak -p 8080:8080 -e KC_HTTP_RELATIVE_PATH=/auth -e KEYC This test suite can run against a fresh unconfigured server instance (no preconfiguration required) and cleans up after itself (undoes all -its config changes) as long as it runs through completly. While its active +its config changes) as long as it runs through completely. While its active it changes the server configuration in the following ways: * creating, modifying and deleting some keycloak groups diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_group_rolemapping/README.md b/ansible_collections/community/general/tests/integration/targets/keycloak_group_rolemapping/README.md new file mode 100644 index 000000000..db58acb7b --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_group_rolemapping/README.md @@ -0,0 +1,21 @@ +<!-- +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 +--> + +# `keycloak_group_rolemapping` Integration Tests + +## Test Server + +Prepare a development server, tested with Keycloak versions tagged 22.0 and 23.0: + +```sh +docker run -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=password --rm quay.io/keycloak/keycloak:22.0 start-dev +``` + +## Run Tests + +```sh +ansible localhost --module-name include_role --args name=keycloak_group_rolemapping +``` diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_group_rolemapping/aliases b/ansible_collections/community/general/tests/integration/targets/keycloak_group_rolemapping/aliases new file mode 100644 index 000000000..9e2cd0dc4 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_group_rolemapping/aliases @@ -0,0 +1,4 @@ +# Copyright (c) 2023, Alexander Groß (@agross) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later +unsupported diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_group_rolemapping/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/keycloak_group_rolemapping/tasks/main.yml new file mode 100644 index 000000000..f1e6371e2 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_group_rolemapping/tasks/main.yml @@ -0,0 +1,160 @@ +# Copyright (c) 2023, Alexander Groß (@agross) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Create realm + community.general.keycloak_realm: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + + id: "{{ realm }}" + realm: "{{ realm }}" + state: present + +- name: Create realm roles + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + + realm: "{{ realm }}" + name: "{{ item }}" + state: present + loop: + - "{{ role_1 }}" + - "{{ role_2 }}" + +- name: Create group + community.general.keycloak_group: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + + realm: "{{ realm }}" + name: "{{ group }}" + state: present + +- name: Map realm roles to group + community.general.keycloak_realm_rolemapping: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + + realm: "{{ realm }}" + group_name: "{{ group }}" + roles: + - name: "{{ role_1 }}" + - name: "{{ role_2 }}" + state: present + register: result + +- name: Assert realm roles are assigned to group + ansible.builtin.assert: + that: + - result is changed + - result.end_state | count == 2 + +- name: Map realm roles to group again (idempotency) + community.general.keycloak_realm_rolemapping: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + + realm: "{{ realm }}" + group_name: "{{ group }}" + roles: + - name: "{{ role_1 }}" + - name: "{{ role_2 }}" + state: present + register: result + +- name: Assert realm roles stay assigned to group + ansible.builtin.assert: + that: + - result is not changed + +- name: Unmap realm role 1 from group + community.general.keycloak_realm_rolemapping: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + + realm: "{{ realm }}" + group_name: "{{ group }}" + roles: + - name: "{{ role_1 }}" + state: absent + register: result + +- name: Assert realm role 1 is unassigned from group + ansible.builtin.assert: + that: + - result is changed + - result.end_state | count == 1 + - result.end_state[0] == role_2 + +- name: Unmap realm role 1 from group again (idempotency) + community.general.keycloak_realm_rolemapping: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + + realm: "{{ realm }}" + group_name: "{{ group }}" + roles: + - name: "{{ role_1 }}" + state: absent + register: result + +- name: Assert realm role 1 stays unassigned from group + ansible.builtin.assert: + that: + - result is not changed + +- name: Unmap realm role 2 from group + community.general.keycloak_realm_rolemapping: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + + realm: "{{ realm }}" + group_name: "{{ group }}" + roles: + - name: "{{ role_2 }}" + state: absent + register: result + +- name: Assert no realm roles are assigned to group + ansible.builtin.assert: + that: + - result is changed + - result.end_state | count == 0 + +- name: Unmap realm role 2 from group again (idempotency) + community.general.keycloak_realm_rolemapping: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + + realm: "{{ realm }}" + group_name: "{{ group }}" + roles: + - name: "{{ role_2 }}" + state: absent + register: result + +- name: Assert no realm roles are assigned to group + ansible.builtin.assert: + that: + - result is not changed + - result.end_state | count == 0 diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_group_rolemapping/vars/main.yml b/ansible_collections/community/general/tests/integration/targets/keycloak_group_rolemapping/vars/main.yml new file mode 100644 index 000000000..0848499e7 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_group_rolemapping/vars/main.yml @@ -0,0 +1,15 @@ +--- +# Copyright (c) 2023, Alexander Groß (@agross) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +url: http://localhost:8080 +admin_realm: master +admin_user: admin +admin_password: password +realm: myrealm + +role_1: myrole-1 +role_2: myrole-2 + +group: mygroup diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_identity_provider/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/keycloak_identity_provider/tasks/main.yml index 79ba33049..afad9740e 100644 --- a/ansible_collections/community/general/tests/integration/targets/keycloak_identity_provider/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_identity_provider/tasks/main.yml @@ -35,14 +35,14 @@ syncMode: FORCE mappers: - name: "first_name" - identityProviderAlias: "oidc-idp" + identityProviderAlias: "{{ idp }}" identityProviderMapper: "oidc-user-attribute-idp-mapper" config: claim: "first_name" user.attribute: "first_name" syncMode: "INHERIT" - name: "last_name" - identityProviderAlias: "oidc-idp" + identityProviderAlias: "{{ idp }}" identityProviderMapper: "oidc-user-attribute-idp-mapper" config: claim: "last_name" @@ -84,14 +84,14 @@ syncMode: FORCE mappers: - name: "first_name" - identityProviderAlias: "oidc-idp" + identityProviderAlias: "{{ idp }}" identityProviderMapper: "oidc-user-attribute-idp-mapper" config: claim: "first_name" user.attribute: "first_name" syncMode: "INHERIT" - name: "last_name" - identityProviderAlias: "oidc-idp" + identityProviderAlias: "{{ idp }}" identityProviderMapper: "oidc-user-attribute-idp-mapper" config: claim: "last_name" @@ -109,7 +109,7 @@ that: - result is not changed -- name: Update existing identity provider (with change) +- name: Update existing identity provider (with change, no mapper change) community.general.keycloak_identity_provider: auth_keycloak_url: "{{ url }}" auth_realm: "{{ admin_realm }}" @@ -132,6 +132,109 @@ - result.existing.enabled == true - result.end_state.enabled == false +- name: Update existing identity provider (delete mapper) + community.general.keycloak_identity_provider: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + alias: "{{ idp }}" + state: present + mappers: + - name: "first_name" + identityProviderAlias: "{{ idp }}" + identityProviderMapper: "oidc-user-attribute-idp-mapper" + config: + claim: "first_name" + user.attribute: "first_name" + syncMode: "INHERIT" + register: result + +- name: Debug + debug: + var: result + +- name: Assert identity provider updated + assert: + that: + - result is changed + - result.existing.mappers | length == 2 + - result.end_state.mappers | length == 1 + - result.end_state.mappers[0].name == "first_name" + +- name: Update existing identity provider (add mapper) + community.general.keycloak_identity_provider: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + alias: "{{ idp }}" + state: present + mappers: + - name: "last_name" + identityProviderAlias: "{{ idp }}" + identityProviderMapper: "oidc-user-attribute-idp-mapper" + config: + claim: "last_name" + user.attribute: "last_name" + syncMode: "INHERIT" + - name: "first_name" + identityProviderAlias: "{{ idp }}" + identityProviderMapper: "oidc-user-attribute-idp-mapper" + config: + claim: "first_name" + user.attribute: "first_name" + syncMode: "INHERIT" + register: result + +- name: Debug + debug: + var: result + +- name: Assert identity provider updated + assert: + that: + - result is changed + - result.existing.mappers | length == 1 + - result.end_state.mappers | length == 2 + +- name: Update existing identity provider (no change, test mapper idempotency) + community.general.keycloak_identity_provider: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + alias: "{{ idp }}" + state: present + mappers: + - name: "last_name" + identityProviderAlias: "{{ idp }}" + identityProviderMapper: "oidc-user-attribute-idp-mapper" + config: + claim: "last_name" + user.attribute: "last_name" + syncMode: "INHERIT" + - name: "first_name" + identityProviderAlias: "{{ idp }}" + identityProviderMapper: "oidc-user-attribute-idp-mapper" + config: + claim: "first_name" + user.attribute: "first_name" + syncMode: "INHERIT" + register: result + +- name: Debug + debug: + var: result + +- name: Assert identity provider updated + assert: + that: + - result is not changed + - name: Delete existing identity provider community.general.keycloak_identity_provider: auth_keycloak_url: "{{ url }}" diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_realm_key/aliases b/ansible_collections/community/general/tests/integration/targets/keycloak_realm_key/aliases new file mode 100644 index 000000000..bd1f02444 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_realm_key/aliases @@ -0,0 +1,5 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +unsupported diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_realm_key/readme.adoc b/ansible_collections/community/general/tests/integration/targets/keycloak_realm_key/readme.adoc new file mode 100644 index 000000000..8e052920c --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_realm_key/readme.adoc @@ -0,0 +1,27 @@ +// Copyright (c) Ansible Project +// GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +// SPDX-License-Identifier: GPL-3.0-or-later + +To be able to run these integration tests a keycloak server must be +reachable under a specific url with a specific admin user and password. +The exact values expected for these parameters can be found in +'vars/main.yml' file. A simple way to do this is to use the official +keycloak docker images like this: + +---- +docker run --name mykeycloak -p 8080:8080 -e KC_HTTP_RELATIVE_PATH=<url-path> -e KEYCLOAK_ADMIN=<admin_user> -e KEYCLOAK_ADMIN_PASSWORD=<admin_password> quay.io/keycloak/keycloak:20.0.2 start-dev +---- + +Example with concrete values inserted: + +---- +docker run --name mykeycloak -p 8080:8080 -e KC_HTTP_RELATIVE_PATH=/auth -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=password quay.io/keycloak/keycloak:20.0.2 start-dev +---- + +This test suite can run against a fresh unconfigured server instance +(no preconfiguration required) and cleans up after itself (undoes all +its config changes) as long as it runs through completely. While its active +it changes the server configuration in the following ways: + + * creating, modifying and deleting some keycloak groups + diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_realm_key/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/keycloak_realm_key/tasks/main.yml new file mode 100644 index 000000000..c02950600 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_realm_key/tasks/main.yml @@ -0,0 +1,373 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later +- name: Remove Keycloak test realm to avoid failures from previous failed runs + community.general.keycloak_realm: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + id: "{{ realm }}" + state: absent + +- name: Create Keycloak test realm + community.general.keycloak_realm: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + id: "{{ realm }}" + state: present + +- name: Create custom realm key (check mode) + community.general.keycloak_realm_key: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: testkey + state: present + parent_id: "{{ realm }}" + config: + private_key: "{{ realm_private_key }}" + certificate: "" + enabled: true + active: true + priority: 150 + check_mode: true + register: result + +- name: Assert that nothing has changed + assert: + that: + - result is changed + - result.end_state != {} + - result.end_state.name == "testkey" + - result.end_state.parentId == "realm_key_test" + - result.end_state.providerId == "rsa" + - result.end_state.providerType == "org.keycloak.keys.KeyProvider" + - result.end_state.config.active == ["true"] + - result.end_state.config.enabled == ["true"] + - result.end_state.config.algorithm == ["RS256"] + - result.end_state.config.priority == ["150"] + - result.msg == "Realm key testkey would be created" + +- name: Create custom realm key + community.general.keycloak_realm_key: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: testkey + state: present + parent_id: "{{ realm }}" + config: + private_key: "{{ realm_private_key }}" + certificate: "" + enabled: true + active: true + priority: 150 + diff: true + register: result + +- name: Assert that realm key was created + assert: + that: + - result is changed + - result.end_state != {} + - result.end_state.name == "testkey" + - result.end_state.parentId == "realm_key_test" + - result.end_state.providerId == "rsa" + - result.end_state.providerType == "org.keycloak.keys.KeyProvider" + - result.end_state.config.active == ["true"] + - result.end_state.config.enabled == ["true"] + - result.end_state.config.algorithm == ["RS256"] + - result.end_state.config.priority == ["150"] + - result.msg == "Realm key testkey created" + +- name: Create custom realm key (test for idempotency) + community.general.keycloak_realm_key: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: testkey + state: present + parent_id: "{{ realm }}" + config: + private_key: "{{ realm_private_key }}" + certificate: "" + enabled: true + active: true + priority: 150 + register: result + +- name: Assert that nothing has changed + assert: + that: + - result is not changed + - result.end_state != {} + - result.end_state.name == "testkey" + - result.end_state.parentId == "realm_key_test" + - result.end_state.providerId == "rsa" + - result.end_state.providerType == "org.keycloak.keys.KeyProvider" + - result.end_state.config.active == ["true"] + - result.end_state.config.enabled == ["true"] + - result.end_state.config.algorithm == ["RS256"] + - result.end_state.config.priority == ["150"] + - result.msg == "Realm key testkey was in sync" + +- name: Update custom realm key (check mode) + community.general.keycloak_realm_key: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: testkey + state: present + parent_id: "{{ realm }}" + config: + private_key: "{{ realm_private_key }}" + certificate: "" + enabled: true + active: true + priority: 140 + check_mode: true + register: result + +- name: Assert that nothing has changed + assert: + that: + - result is changed + - result.end_state != {} + - result.end_state.name == "testkey" + - result.end_state.parentId == "realm_key_test" + - result.end_state.providerId == "rsa" + - result.end_state.providerType == "org.keycloak.keys.KeyProvider" + - result.end_state.config.active == ["true"] + - result.end_state.config.enabled == ["true"] + - result.end_state.config.algorithm == ["RS256"] + - result.end_state.config.priority == ["140"] + - result.msg == "Realm key testkey would be changed: config.priority ['150'] -> ['140']" + +- name: Update custom realm key + community.general.keycloak_realm_key: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: testkey + state: present + parent_id: "{{ realm }}" + config: + private_key: "{{ realm_private_key }}" + certificate: "" + enabled: true + active: true + priority: 140 + diff: true + register: result + +- name: Assert that realm key was updated + assert: + that: + - result is changed + - result.end_state != {} + - result.end_state.name == "testkey" + - result.end_state.parentId == "realm_key_test" + - result.end_state.providerId == "rsa" + - result.end_state.providerType == "org.keycloak.keys.KeyProvider" + - result.end_state.config.active == ["true"] + - result.end_state.config.enabled == ["true"] + - result.end_state.config.algorithm == ["RS256"] + - result.end_state.config.priority == ["140"] + - result.msg == "Realm key testkey changed: config.priority ['150'] -> ['140']" + +- name: Update custom realm key (test for idempotency) + community.general.keycloak_realm_key: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: testkey + state: present + parent_id: "{{ realm }}" + config: + private_key: "{{ realm_private_key }}" + certificate: "" + enabled: true + active: true + priority: 140 + register: result + +- name: Assert that nothing has changed + assert: + that: + - result is not changed + - result.end_state != {} + - result.end_state.name == "testkey" + - result.end_state.parentId == "realm_key_test" + - result.end_state.providerId == "rsa" + - result.end_state.providerType == "org.keycloak.keys.KeyProvider" + - result.end_state.config.active == ["true"] + - result.end_state.config.enabled == ["true"] + - result.end_state.config.algorithm == ["RS256"] + - result.end_state.config.priority == ["140"] + - result.msg == "Realm key testkey was in sync" + +- name: Force update custom realm key + community.general.keycloak_realm_key: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: testkey + force: true + state: present + parent_id: "{{ realm }}" + config: + private_key: "{{ realm_private_key_2 }}" + certificate: "" + enabled: true + active: true + priority: 140 + register: result + +- name: Assert that forced update ran correctly + assert: + that: + - result is changed + - result.end_state != {} + - result.end_state.name == "testkey" + - result.end_state.parentId == "realm_key_test" + - result.end_state.providerId == "rsa" + - result.end_state.providerType == "org.keycloak.keys.KeyProvider" + - result.end_state.config.active == ["true"] + - result.end_state.config.enabled == ["true"] + - result.end_state.config.algorithm == ["RS256"] + - result.end_state.config.priority == ["140"] + - result.msg == "Realm key testkey was forcibly updated" + +- name: Remove custom realm key + community.general.keycloak_realm_key: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: testkey + state: absent + parent_id: "{{ realm }}" + config: + private_key: "{{ realm_private_key }}" + certificate: "" + priority: 140 + diff: true + register: result + +- name: Assert that realm key was deleted + assert: + that: + - result is changed + - result.end_state == {} + - result.msg == "Realm key testkey deleted" + +- name: Remove custom realm key (test for idempotency) + community.general.keycloak_realm_key: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: testkey + state: absent + parent_id: "{{ realm }}" + config: + private_key: "{{ realm_private_key }}" + certificate: "" + priority: 140 + register: result + +- name: Assert that nothing has changed + assert: + that: + - result is not changed + - result.end_state == {} + - result.msg == "Realm key testkey not present" + +- name: Create custom realm key with a custom certificate + community.general.keycloak_realm_key: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: testkey_with_certificate + state: present + parent_id: "{{ realm }}" + config: + private_key: "{{ realm_private_key }}" + certificate: "{{ realm_certificate }}" + enabled: true + active: true + priority: 150 + diff: true + register: result + +- name: Assert that realm key with custom certificate was created + assert: + that: + - result is changed + - result.end_state != {} + - result.end_state.name == "testkey_with_certificate" + - result.end_state.parentId == "realm_key_test" + - result.end_state.providerId == "rsa" + - result.end_state.providerType == "org.keycloak.keys.KeyProvider" + - result.end_state.config.active == ["true"] + - result.end_state.config.enabled == ["true"] + - result.end_state.config.algorithm == ["RS256"] + - result.end_state.config.priority == ["150"] + - result.msg == "Realm key testkey_with_certificate created" + +- name: Attempt to change the private key and the certificate + community.general.keycloak_realm_key: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: testkey_with_certificate + state: present + parent_id: "{{ realm }}" + config: + private_key: "a different private key string" + certificate: "a different certificate string" + enabled: true + active: true + priority: 150 + diff: true + register: result + +- name: Assert that nothing has changed + assert: + that: + - result is not changed + - result.end_state != {} + - result.end_state.name == "testkey_with_certificate" + - result.end_state.parentId == "realm_key_test" + - result.end_state.providerId == "rsa" + - result.end_state.providerType == "org.keycloak.keys.KeyProvider" + - result.end_state.config.active == ["true"] + - result.end_state.config.enabled == ["true"] + - result.end_state.config.algorithm == ["RS256"] + - result.end_state.config.priority == ["150"] + - result.msg == "Realm key testkey_with_certificate was in sync" + +- name: Remove Keycloak test realm + community.general.keycloak_realm: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + id: "{{ realm }}" + state: absent diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_realm_key/vars/main.yml b/ansible_collections/community/general/tests/integration/targets/keycloak_realm_key/vars/main.yml new file mode 100644 index 000000000..d39cf8f73 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_realm_key/vars/main.yml @@ -0,0 +1,48 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +url: http://localhost:8080/auth +admin_realm: master +admin_user: admin +admin_password: password +realm: realm_key_test +realm_private_key_name: testkey +realm_private_key: | + -----BEGIN PRIVATE KEY----- + MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC9Mi7IKXPhqGiWGwgEYEXnqc8nytG1pHbC6QYZe1gUa43jDtGYQln36It02BGw4e5XydCUj+M26X2sH+kKaV+KHEnJtcEqdAuVX1QaDVzeiOoo1/B9HC8By6NZBsOSdxpat3EvilQ+R7NP9yi53J08+vfeZSEGyPmKV1me7nJnRh3/zcRsOi92GTsBd7gApKfP8sorDjY8m9NRuPLwleK2nh/aRvj1yK8x3UAqUIbOCVaE39bSN6VUTFK2Q/+MX3vF0Zugsk7PKKmfqcEW6wj7dtSElbX4uhrfTkGMmwIWdIiLDNRA/jVRvGxUB1SyMy6kmMC8jC2QGWpZgfkSKtHlAgMBAAECggEACWkSVh7ntmjtwM+Z47vVJkt2NBS8vxPt206DYOeXbzaVUV6mkrP0LSZKL3bi1GE8fW3am9UXWF8fQt04dm3c1G4JRojtkXrBq72Y3Y3eGWyGdx8chWCOPwDdwFsbhbC6ZRo8PUDcZVekJd1Vj38XbBXQl+WAUcnTzauAF+1kz9mhJq1gpglIbB+8l7VjMXwXeaGWJQ5OL/MSsq7r3P1elVjHwprFBM7HHA5+RTu/KY/GcEutgm5uwTRqRZNC1IBXAQtBO7HQJbuLqDPTQ3RRCPEur8R+0dk5bF+8IyzQ8Bh+Dhuou9xzfS/A7lV6L/CZSpv4Bvq1H3Uxk+orXf2Q2QKBgQDBOf1nSJB0VgQdIcdtgVpVgQ2SyWAd+N8Qk7QsyVQf9f7ZqiFLejWJbaaeY9WtfZ01D8tgHJfPqsO1/Jux255mtkyk2K2c6dav1Lsd4l+iPfidsDJNWkcd59nQqwC9BLjzWK/J4rO20apm34abLaZ9oVk8Mgz8VWJWOxTgCr+COQKBgQD6qP1lm6rzlCSIEz9eCuGPkQkVo+NIP437e3i+sxtkLlMgnmfzSwSJdVF8AKH3gXi3NyWjfBVYeAZEkm1kHF8IWOiK4U1y95Vx3uud3NX4SC+cjePc+pDPQJiz9L+zq9I6WFZWmm7n/9heTxu/l0vxI4FHaBmt95BMwLJNkzbdDQKBgCHGwUUMqjOr1YxCG1pJAkFwDa9bBDI5DsUXDKfHia0Mkz/5PVi0RCeBw15slS1+h7x+xk5GsULb1to5Df5JJadOtpcaST7koWKbDRpsN8tkidEGu8RJw6S2opyXR8nCyZHALvpbZo7Ol7rj1+PIVxIe4jpjhWGWi1oHed6wAkoBAoGAJx2F5XxEUhx1EvMF+XPzPQciBsl7Z0PbsTnUXtXuWVTNThLKH/I99AFlxNcIb2o530VwzzFG13Zra/n5rhyrS88sArgj8OPn40wpMopKraL+Iw0VWN+VB3KKIdL4s14FwWsVlhAlbHjFV/o6V0yR4kBrJSx+jWJLl16etHJbpmUCgYBUWCQwcT1aw9XHWJXiNYTnQSYg88hgGYhts1qSzhfu+n1t2BlAlxM0gu2+gez21mM8uiYsqbU2OZeG2U4as6kdai8Q4tzNQt2f1r3ZewJN/QHrkx6FT94PNa0w4ILiQ9Eu7xssaHcYjHyrI1NlbMKypVy6waDG2ajLOFAVeHGpOg== + -----END PRIVATE KEY----- +realm_certificate: | + -----BEGIN CERTIFICATE----- + MIIDQDCCAiigAwIBAgIUMfPlHWcZn6xfeSjfbhgmt4yy6mMwDQYJKoZIhvcNAQELBQAwQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDAeFw0yMzA4MTgxMTU5MDFaFw0zMzA4MTUxMTU5MDFaMEIxCzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9Mi7IKXPhqGiWGwgEYEXnqc8nytG1pHbC6QYZe1gUa43jDtGYQln36It02BGw4e5XydCUj+M26X2sH+kKaV+KHEnJtcEqdAuVX1QaDVzeiOoo1/B9HC8By6NZBsOSdxpat3EvilQ+R7NP9yi53J08+vfeZSEGyPmKV1me7nJnRh3/zcRsOi92GTsBd7gApKfP8sorDjY8m9NRuPLwleK2nh/aRvj1yK8x3UAqUIbOCVaE39bSN6VUTFK2Q/+MX3vF0Zugsk7PKKmfqcEW6wj7dtSElbX4uhrfTkGMmwIWdIiLDNRA/jVRvGxUB1SyMy6kmMC8jC2QGWpZgfkSKtHlAgMBAAGjLjAsMAsGA1UdDwQEAwIEkDAdBgNVHQ4EFgQUcZirWRV5EzRhanUVSQ9rmAavVbEwDQYJKoZIhvcNAQELBQADggEBAIt2aFr/sxvtZfDc+Nb9tgspBuoX8f9Gf9mrS6dTdvdqSMHQrcoejSEEAZNljdSpKAhnhyR3+uCIev++WS4tixZoooQ8aYxDGNIwyry51GNEK7LKXVRmkbZFODidRuYZ1XWQORaJoaXWplaPaNtLvUr1swachz36K4n8/UIi109w/addajOHFbFGAzUmGRR4saMZPGrQCaNFje7G1o5wb/mQD1L+Jfk81Id5/F6NFBsSEIi+/O7Xs7fOWuab6cdfwI7zQQclEo55WQkLXefFLn+Ju0Ftgl023awpNEE4pjl6jD5VSEOkQ+I2sxGvymgjz7Av4zPOD/Lr05lRnMxf8dA= + -----END CERTIFICATE----- +realm_private_key_2: | + -----BEGIN PRIVATE KEY----- + MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCyQ5FKuqbnWEtt + KI0FHKFvd+G/RyEI2ow29Ytjs3fZ7/gMYfXozHLLJl3jgCOvSf9Ta55arL0XnCCf + RKQb0vpgMmOTQw++A1UmNXe8atTczZMRiHMHFdLhXUvUKthcMGOrTH8xegCnm0bG + rZwimjQDog/kROMAN78Uv8SD1lMpGBxPr2DWXNl4kRF670m/jC0cM7SeDGCCKVF5 + SEh6rMDgI62AxKnbtxuAbF9SOO/6kTsYv5+dc8wxDEb0aaT1jC1CLhjAVmjc6vO7 + WHE0LLas+ARs4ghMONLN6VdOkJxBuEtlLqM3M+/viD1TRftZCeLarYLWdEsg8Yz9 + Ufb0oawzAgMBAAECggEARqPDxWsljHNOoFj7WNU5m6RTzqpvCsUf3v96Vu3dRn1z + O+Ttv2yU6K+xcN9sRJ/8D6CLxb7Bx8NUoghfR69ZDBmrn8VpTZCgg12Yrw9efojw + CHibrGkXgbqou9CmoBGEzXKozIBeFgzQBRby0jts9SuZRImPspxkmeJMCzo5BgUg + ksNibaWikvUJYMgFc7PdXEvxhCKcWTTGC3fxJwpRxXkqKsYDa3JhdhloH8hHqynm + o7WEXeGAn4UV7C1tg3OdTciHn/ONMRItPcyonwk19meZTvsEub6ZsNjVg/5oJVBr + WG8vPZBi1VzAMayDXxDOnEAKW5eJXRSNX1vZ7EQTEQKBgQDXg5pSp9hVdVZc+eN/ + Ab/1NMMdgrQdbyTeB9esjLiwNuXysQm/KaG8gTkLpiKVvJ8R7SOcxb9Y5Gt9Y5Ej + eu943V4zLDIzNt/ST4bXGW/gQ84zkMBdhKz9hKA5tartVjI1ycznjpDbgn/jAYPI + 8VXGmjID2oDIJ7P+dLD8lMBDvQKBgQDTwIyimy+4EwFUuuppfWArXRsqsWUScGWD + +06xbc+Ld92LJBvakvSTdDNnS/PlYGl/fJjqQ4wq5UPREJYCi3UW9I5jtfsIg8Pl + oCnIhEYkn8xPZ7X8grU4emkM6QAPhstCDlXE6t0T202TpYVYjtEEDRQu4rKAbJ0h + gqSh5Ge2rwKBgEjrx6jWEBYCaOF20ComTmxKmQaANi+Lbt8NqkVBLDC7spymmJSt + IoOk+cdeRG+D7hLjuVwPcQpD57b6nJ5zt1mfFYOdHbNEiwEfVZGskrVAXCIIhX5f + KSVy3cAJHzfFJaIbkRB8pbkQc/M8jPnN5ucXP3scUNzoyjd8BnLAZjnFAoGAWwwY + rDYTz48EbH0uG4uYFS0kaDf8YHBJhfVBgdLYgXxZmuE8xL+ZP+mfzJOA3CiXVASr + 71Z551vKzBLYnWF/SA6BRuhRdvjI+2vha2FMk6TOAXpzao59AzrG/pEUwJhRvyZQ + xKnDwyzxb0GlU02dG6PQANTisYuCCI2W4jFGUusCgYB72p5o5uBr7qrFMTdMMxxe + f/9Go/9QBR/uNYk3D/rWj0F/bXGbiYMddNMD4v3XE24NL4ZvBJn0Po64Tuz5+wtu + 5ICKc6ED1l55MPsKdegVMpXGIFRjZt2TtCk4FE68m5QJpT1IIK7I9jv0+FGKjFYa + ukdTEghu13cANd8eKpxBsQ== + -----END PRIVATE KEY----- diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_role/README.md b/ansible_collections/community/general/tests/integration/targets/keycloak_role/README.md new file mode 100644 index 000000000..ccb4c8ffa --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_role/README.md @@ -0,0 +1,20 @@ +<!-- +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 +--> +# Running keycloak_user module integration test + +To run Keycloak user module's integration test, start a keycloak server using Docker or Podman: + + podman|docker run -d --rm --name mykeycloak -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=password quay.io/keycloak/keycloak:latest start-dev --http-relative-path /auth + +Source Ansible env-setup from ansible github repository + +Run integration tests: + + ansible-test integration keycloak_role --python 3.10 --allow-unsupported + +Cleanup: + + podman|docker stop mykeycloak diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_role/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/keycloak_role/tasks/main.yml index 61b62629a..c649b8680 100644 --- a/ansible_collections/community/general/tests/integration/targets/keycloak_role/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_role/tasks/main.yml @@ -248,3 +248,236 @@ that: - result is not changed - result.end_state == {} + +- name: Create realm role with composites + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "{{ keycloak_role_name }}" + realm: "{{ realm }}" + description: "{{ keycloak_role_description }}" + composite: "{{ keycloak_role_composite }}" + composites: "{{ keycloak_role_composites }}" + state: present + register: result + +- name: Debug + debug: + var: result + +- name: Assert realm role is created with composites + assert: + that: + - result is changed + - result.end_state.composites | length == 3 + +- name: Change realm role with composites no change + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "{{ keycloak_role_name }}" + realm: "{{ realm }}" + description: "{{ keycloak_role_description }}" + composite: "{{ keycloak_role_composite }}" + composites: "{{ keycloak_role_composites }}" + state: present + register: result + +- name: Debug + debug: + var: result + +- name: Assert realm role with composites have not changed + assert: + that: + - result is not changed + - result.end_state.composites | length == 3 + +- name: Remove composite from realm role with composites + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "{{ keycloak_role_name }}" + realm: "{{ realm }}" + description: "{{ keycloak_role_description }}" + composite: "{{ keycloak_role_composite }}" + composites: "{{ keycloak_role_composites_with_absent }}" + state: present + register: result + +- name: Debug + debug: + var: result + +- name: Assert composite was removed from realm role with composites + assert: + that: + - result is changed + - result.end_state.composites | length == 2 + +- name: Delete realm role with composites + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + name: "{{ keycloak_role_name }}" + state: absent + register: result + +- name: Debug + debug: + var: result + +- name: Assert realm role deleted + assert: + that: + - result is changed + - result.end_state == {} + +- name: Delete absent realm role with composites + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + name: "{{ keycloak_role_name }}" + state: absent + register: result + +- name: Debug + debug: + var: result + +- name: Assert not changed and realm role absent + assert: + that: + - result is not changed + - result.end_state == {} + +- name: Create client role with composites + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "{{ keycloak_role_name }}" + client_id: "{{ client_id }}" + realm: "{{ realm }}" + description: "{{ keycloak_role_description }}" + composite: "{{ keycloak_role_composite }}" + composites: "{{ keycloak_role_composites }}" + state: present + register: result + +- name: Debug + debug: + var: result + +- name: Assert client role is created with composites + assert: + that: + - result is changed + - result.end_state.composites | length == 3 + +- name: Change client role with composites no change + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "{{ keycloak_role_name }}" + client_id: "{{ client_id }}" + realm: "{{ realm }}" + description: "{{ keycloak_role_description }}" + composite: "{{ keycloak_role_composite }}" + composites: "{{ keycloak_role_composites }}" + state: present + register: result + +- name: Debug + debug: + var: result + +- name: Assert client role with composites have not changed + assert: + that: + - result is not changed + - result.end_state.composites | length == 3 + +- name: Remove composite from client role with composites + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "{{ keycloak_role_name }}" + client_id: "{{ client_id }}" + realm: "{{ realm }}" + description: "{{ keycloak_role_description }}" + composite: "{{ keycloak_role_composite }}" + composites: "{{ keycloak_role_composites_with_absent }}" + state: present + register: result + +- name: Debug + debug: + var: result + +- name: Assert composite was removed from client role with composites + assert: + that: + - result is changed + - result.end_state.composites | length == 2 + +- name: Delete client role with composites + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + name: "{{ keycloak_role_name }}" + client_id: "{{ client_id }}" + state: absent + register: result + +- name: Debug + debug: + var: result + +- name: Assert client role deleted + assert: + that: + - result is changed + - result.end_state == {} + +- name: Delete absent client role with composites + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + name: "{{ keycloak_role_name }}" + client_id: "{{ client_id }}" + state: absent + register: result + +- name: Debug + debug: + var: result + +- name: Assert not changed and client role absent + assert: + that: + - result is not changed + - result.end_state == {}
\ No newline at end of file diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_role/vars/main.yml b/ansible_collections/community/general/tests/integration/targets/keycloak_role/vars/main.yml index b003311e0..0af55dfc5 100644 --- a/ansible_collections/community/general/tests/integration/targets/keycloak_role/vars/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_role/vars/main.yml @@ -12,3 +12,30 @@ client_id: myclient role: myrole description_1: desc 1 description_2: desc 2 + +keycloak_role_name: test +keycloak_role_description: test +keycloak_role_composite: true +keycloak_role_composites: + - name: view-clients + client_id: "realm-management" + state: present + - name: query-clients + client_id: "realm-management" + state: present + - name: offline_access + state: present +keycloak_client_id: test-client +keycloak_client_name: test-client +keycloak_client_description: This is a client for testing purpose +role_state: present + +keycloak_role_composites_with_absent: + - name: view-clients + client_id: "realm-management" + state: present + - name: query-clients + client_id: "realm-management" + state: present + - name: offline_access + state: absent
\ No newline at end of file diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_user/README.md b/ansible_collections/community/general/tests/integration/targets/keycloak_user/README.md new file mode 100644 index 000000000..07ecc3f83 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_user/README.md @@ -0,0 +1,21 @@ +<!-- +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 +--> +# Running keycloak_user module integration test + +To run Keycloak user module's integration test, start a keycloak server using Docker or Podman: + + podman|docker run -d --rm --name mykeycloak -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=password quay.io/keycloak/keycloak:latest start-dev --http-relative-path /auth + +Source Ansible env-setup from ansible github repository + +Run integration tests: + + ansible-test integration keycloak_user --python 3.10 --allow-unsupported + +Cleanup: + + podman|docker stop mykeycloak + diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_user/aliases b/ansible_collections/community/general/tests/integration/targets/keycloak_user/aliases new file mode 100644 index 000000000..0abc6a467 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_user/aliases @@ -0,0 +1,4 @@ +# Copyright (c) 2023, INSPQ Philippe Gauthier (@elfelip) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later +unsupported diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_user/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/keycloak_user/tasks/main.yml new file mode 100644 index 000000000..0f1fe152d --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_user/tasks/main.yml @@ -0,0 +1,114 @@ +# Copyright (c) 2022, Dušan Marković (@bratwurzt) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Create realm + community.general.keycloak_realm: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + id: "{{ realm }}" + realm: "{{ realm }}" + state: present + +- name: Create new realm role + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + name: "{{ role }}" + description: "{{ description_1 }}" + state: present + +- name: Create client + community.general.keycloak_client: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_id }}" + service_accounts_enabled: true + state: present + register: client + + +- name: Create new client role + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_id }}" + name: "{{ keycloak_client_role }}" + description: "{{ description_1 }}" + state: present + +- name: Create new groups + community.general.keycloak_group: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + name: "{{ item.name }}" + state: present + with_items: "{{ keycloak_user_groups }}" + +- name: Create user + community.general.keycloak_user: + auth_keycloak_url: "{{ url }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + auth_realm: "{{ admin_realm }}" + username: "{{ keycloak_username }}" + realm: "{{ realm }}" + first_name: Ceciestes + last_name: Untestes + email: ceciestuntestes@test.com + groups: "{{ keycloak_user_groups }}" + attributes: "{{ keycloak_user_attributes }}" + state: present + register: create_result + +- name: debug + debug: + var: create_result + +- name: Assert user is created + assert: + that: + - create_result.changed + - create_result.end_state.username == 'test' + - create_result.end_state.attributes | length == 3 + - create_result.end_state.groups | length == 2 + +- name: Delete User + community.general.keycloak_user: + auth_keycloak_url: "{{ url }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + auth_realm: "{{ admin_realm }}" + username: "{{ keycloak_username }}" + realm: "{{ realm }}" + first_name: Ceciestes + last_name: Untestes + email: ceciestuntestes@test.com + groups: "{{ keycloak_user_groups }}" + attributes: "{{ keycloak_user_attributes }}" + state: absent + register: delete_result + +- name: debug + debug: + var: delete_result + +- name: Assert user is deleted + assert: + that: + - delete_result.changed + - delete_result.end_state | length == 0 diff --git a/ansible_collections/community/general/tests/integration/targets/keycloak_user/vars/main.yml b/ansible_collections/community/general/tests/integration/targets/keycloak_user/vars/main.yml new file mode 100644 index 000000000..9962aba54 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/keycloak_user/vars/main.yml @@ -0,0 +1,46 @@ +--- +# Copyright (c) 2022, Dušan Marković (@bratwurzt) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +url: http://localhost:8080/auth +admin_realm: master +admin_user: admin +admin_password: password +realm: myrealm +client_id: myclient +role: myrole +description_1: desc 1 +description_2: desc 2 + +keycloak_username: test +keycloak_service_account_client_id: "{{ client_id }}" +keycloak_user_realm_roles: + - name: offline_access + - name: "{{ role }}" +keycloak_client_role: test +keycloak_user_client_roles: + - client_id: "{{ client_id }}" + roles: + - name: "{{ keycloak_client_role }}" + - client_id: "{{ realm }}-realm" + roles: + - name: view-users + - name: query-users +keycloak_user_attributes: + - name: attr1 + values: + - value1s + state: present + - name: attr2 + values: + - value2s + state: present + - name: attr3 + values: + - value3s + state: present +keycloak_user_groups: + - name: test + state: present + - name: test2 diff --git a/ansible_collections/community/general/tests/integration/targets/ldap_search/tasks/tests/auth.yml b/ansible_collections/community/general/tests/integration/targets/ldap_search/tasks/tests/auth.yml new file mode 100644 index 000000000..a8c7a13ee --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/ldap_search/tasks/tests/auth.yml @@ -0,0 +1,47 @@ +--- +# 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 + +- debug: + msg: Running tests/auth.yml + +#################################################################### +## Search ########################################################## +#################################################################### +- name: Test simple search for password authenticated user + ldap_search: + dn: "ou=users,dc=example,dc=com" + scope: "onelevel" + filter: "(uid=ldaptest)" + bind_dn: "uid=ldaptest,ou=users,dc=example,dc=com" + bind_pw: "test1pass!" + ignore_errors: true + register: output + +- name: assert that test LDAP user can read its password + assert: + that: + - output is not failed + - output.results | length == 1 + - output.results.0.userPassword is defined + +- name: Test simple search for cert authenticated user + ldap_search: + dn: "ou=users,dc=example,dc=com" + server_uri: "ldap://localhost/" + start_tls: true + ca_path: /usr/local/share/ca-certificates/ca.crt + scope: "onelevel" + filter: "(uid=ldaptest)" + client_cert: "/root/user.crt" + client_key: "/root/user.key" + ignore_errors: true + register: output + +- name: assert that test LDAP user can read its password + assert: + that: + - output is not failed + - output.results | length == 1 + - output.results.0.userPassword is defined diff --git a/ansible_collections/community/general/tests/integration/targets/ldap_search/tasks/tests/basic.yml b/ansible_collections/community/general/tests/integration/targets/ldap_search/tasks/tests/basic.yml index 36d245d39..11e5d6562 100644 --- a/ansible_collections/community/general/tests/integration/targets/ldap_search/tasks/tests/basic.yml +++ b/ansible_collections/community/general/tests/integration/targets/ldap_search/tasks/tests/basic.yml @@ -23,3 +23,17 @@ - output is not failed - output.results | length == 1 - output.results.0.displayName == "LDAP Test" + +- name: Test simple search for a user with no results + ldap_search: + dn: "ou=users,dc=example,dc=com" + scope: "onelevel" + filter: "(uid=nonexistent)" + ignore_errors: true + register: output + +- name: assert that the output is empty + assert: + that: + - output is not failed + - output.results | length == 0 diff --git a/ansible_collections/community/general/tests/integration/targets/ldap_search/tasks/tests/pages.yml b/ansible_collections/community/general/tests/integration/targets/ldap_search/tasks/tests/pages.yml new file mode 100644 index 000000000..32575854b --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/ldap_search/tasks/tests/pages.yml @@ -0,0 +1,24 @@ +--- +# 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 + +- debug: + msg: Running tests/pages.yml + +#################################################################### +## Search ########################################################## +#################################################################### +- name: Test paged search for all users + ldap_search: + dn: "ou=users,dc=example,dc=com" + scope: "onelevel" + page_size: 1 + ignore_errors: true + register: output + +- name: assert that the right number of results are returned + assert: + that: + - output is not failed + - output.results | length == 2 diff --git a/ansible_collections/community/general/tests/integration/targets/ldap_search/tasks/tests/schema.yml b/ansible_collections/community/general/tests/integration/targets/ldap_search/tasks/tests/schema.yml new file mode 100644 index 000000000..892eac3cb --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/ldap_search/tasks/tests/schema.yml @@ -0,0 +1,25 @@ +--- +# 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 + +- debug: + msg: Running tests/schema.yml + +#################################################################### +## Search ########################################################## +#################################################################### +- name: Test for ldap schema output + ldap_search: + dn: "ou=users,dc=example,dc=com" + scope: "onelevel" + schema: true + ignore_errors: true + register: output + +- name: Assert that the schema output is correct + assert: + that: + - output is not failed + - output.results | length >= 1 + - "{{ 'displayName' in output.results.0.attrs }}" diff --git a/ansible_collections/community/general/tests/integration/targets/listen_ports_facts/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/listen_ports_facts/tasks/main.yml index 70649f505..0e583e7a1 100644 --- a/ansible_collections/community/general/tests/integration/targets/listen_ports_facts/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/listen_ports_facts/tasks/main.yml @@ -13,7 +13,7 @@ ansible.builtin.package: name: - net-tools - - netcat + - netcat-openbsd state: latest when: ansible_os_family == "Debian" diff --git a/ansible_collections/community/general/tests/integration/targets/locale_gen/aliases b/ansible_collections/community/general/tests/integration/targets/locale_gen/aliases index f7f4063f6..a5d3e27f9 100644 --- a/ansible_collections/community/general/tests/integration/targets/locale_gen/aliases +++ b/ansible_collections/community/general/tests/integration/targets/locale_gen/aliases @@ -6,3 +6,5 @@ azp/posix/3 destructive needs/root skip/aix +skip/freebsd +skip/macos diff --git a/ansible_collections/community/general/tests/integration/targets/locale_gen/tasks/basic.yml b/ansible_collections/community/general/tests/integration/targets/locale_gen/tasks/basic.yml new file mode 100644 index 000000000..8718e0be8 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/locale_gen/tasks/basic.yml @@ -0,0 +1,102 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Is the locale we're going to test against installed? {{ locale_basic.localegen }} + command: locale -a + register: initial_state + ignore_errors: true + +- name: Make sure the locale is not installed {{ locale_basic.localegen }} + locale_gen: + name: "{{ locale_basic.localegen }}" + state: absent + +- name: Is the locale present? {{ locale_basic.localegen }} + command: locale -a + register: cleaned + ignore_errors: true + +- name: Make sure the locale is not present {{ locale_basic.localegen }} + assert: + that: + - locale_basic.skip_removal or locale_basic.locales | intersect(cleaned.stdout_lines) == [] + +- name: Install the locale {{ locale_basic.localegen }} + locale_gen: + name: "{{ locale_basic.localegen }}" + state: present + register: output_present + +- name: Is the locale present? {{ locale_basic.localegen }} + command: locale -a + register: post_check_output_present + ignore_errors: true + +- name: Make sure the locale is present and we say we installed it {{ locale_basic.localegen }} + assert: + that: + - locale_basic.locales | intersect(post_check_output_present.stdout_lines) != [] + - locale_basic.skip_removal or output_present is changed + +- name: Install the locale a second time {{ locale_basic.localegen }} + locale_gen: + name: "{{ locale_basic.localegen }}" + state: present + register: output_present_idempotent + +- name: Is the locale present? {{ locale_basic.localegen }} + command: locale -a + register: post_check_output_present_idempotent + ignore_errors: true + +- name: Make sure the locale is present and we reported no change {{ locale_basic.localegen }} + assert: + that: + - locale_basic.locales | intersect(post_check_output_present_idempotent.stdout_lines) != [] + - output_present_idempotent is not changed + +- name: Removals + when: locale_basic.skip_removal is false + block: + - name: Remove the locale {{ locale_basic.localegen }} + locale_gen: + name: "{{ locale_basic.localegen }}" + state: absent + register: output_absent + + - name: Is the locale present? {{ locale_basic.localegen }} + command: locale -a + register: post_check_output_absent + ignore_errors: true + + - name: Make sure the locale is absent and we reported a change {{ locale_basic.localegen }} + assert: + that: + - locale_basic.locales | intersect(post_check_output_absent.stdout_lines) == [] + - output_absent is changed + + - name: Remove the locale a second time {{ locale_basic.localegen }} + locale_gen: + name: "{{ locale_basic.localegen }}" + state: absent + register: output_absent_idempotent + + - name: Is the locale present? {{ locale_basic.localegen }} + command: locale -a + register: post_check_output_absent_idempotent + ignore_errors: true + + - name: Make sure the locale is absent and we reported no change {{ locale_basic.localegen }} + assert: + that: + - locale_basic.locales | intersect(post_check_output_absent_idempotent.stdout_lines) == [] + - output_absent_idempotent is not changed + +# Cleanup +- name: Reinstall the locale we tested against if it was initially installed {{ locale_basic.localegen }} + locale_gen: + name: "{{ locale_basic.localegen }}" + state: present + when: locale_basic.locales | intersect(initial_state.stdout_lines) != [] diff --git a/ansible_collections/community/general/tests/integration/targets/locale_gen/tasks/locale_gen.yml b/ansible_collections/community/general/tests/integration/targets/locale_gen/tasks/locale_gen.yml deleted file mode 100644 index c6bdcc046..000000000 --- a/ansible_collections/community/general/tests/integration/targets/locale_gen/tasks/locale_gen.yml +++ /dev/null @@ -1,99 +0,0 @@ ---- -# Copyright (c) Ansible Project -# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) -# SPDX-License-Identifier: GPL-3.0-or-later - -- name: Is the locale we're going to test against installed? - shell: locale -a | grep pt_BR - register: initial_state - ignore_errors: true - -- name: Make sure the locale is not installed - locale_gen: - name: pt_BR - state: absent - -- name: Is the locale present? - shell: locale -a | grep pt_BR - register: cleaned - ignore_errors: true - -- name: Make sure the locale is not present - assert: - that: - - "cleaned.rc == 1" - -- name: Install the locale - locale_gen: - name: pt_BR - state: present - register: output - -- name: Is the locale present? - shell: locale -a | grep pt_BR - register: post_check_output - ignore_errors: true - -- name: Make sure the locale is present and we say we installed it - assert: - that: - - "post_check_output.rc == 0" - - "output.changed" - -- name: Install the locale a second time - locale_gen: - name: pt_BR - state: present - register: output - -- name: Is the locale present? - shell: locale -a | grep pt_BR - register: post_check_output - ignore_errors: true - -- name: Make sure the locale is present and we reported no change - assert: - that: - - "post_check_output.rc == 0" - - "not output.changed" - -- name: Remove the locale - locale_gen: - name: pt_BR - state: absent - register: output - -- name: Is the locale present? - shell: locale -a | grep pt_BR - register: post_check_output - ignore_errors: true - -- name: Make sure the locale is absent and we reported a change - assert: - that: - - "post_check_output.rc == 1" - - "output.changed" - -- name: Remove the locale a second time - locale_gen: - name: pt_BR - state: absent - register: output - -- name: Is the locale present? - shell: locale -a | grep pt_BR - register: post_check_output - ignore_errors: true - -- name: Make sure the locale is absent and we reported no change - assert: - that: - - "post_check_output.rc == 1" - - "not output.changed" - -# Cleanup -- name: Reinstall the locale we tested against if it was initially installed - locale_gen: - name: pt_BR - state: present - when: initial_state.rc == 0 diff --git a/ansible_collections/community/general/tests/integration/targets/locale_gen/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/locale_gen/tasks/main.yml index de3e673be..2d9dfcee0 100644 --- a/ansible_collections/community/general/tests/integration/targets/locale_gen/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/locale_gen/tasks/main.yml @@ -8,5 +8,11 @@ # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # SPDX-License-Identifier: GPL-3.0-or-later -- include_tasks: 'locale_gen.yml' - when: ansible_distribution in ('Ubuntu', 'Debian') +- name: Bail out if not supported + ansible.builtin.meta: end_play + when: ansible_distribution not in ('Ubuntu', 'Debian') + +- include_tasks: basic.yml + loop: "{{ locale_list_basic }}" + loop_control: + loop_var: locale_basic diff --git a/ansible_collections/community/general/tests/integration/targets/locale_gen/vars/main.yml b/ansible_collections/community/general/tests/integration/targets/locale_gen/vars/main.yml new file mode 100644 index 000000000..44327ddd3 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/locale_gen/vars/main.yml @@ -0,0 +1,17 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# locale_basic: pt_BR + +locale_list_basic: + - localegen: pt_BR + locales: [pt_BR] + skip_removal: false + - localegen: C.UTF-8 + locales: [C.utf8, C.UTF-8] + skip_removal: true + - localegen: eo + locales: [eo] + skip_removal: false diff --git a/ansible_collections/community/general/tests/integration/targets/lookup_cartesian/aliases b/ansible_collections/community/general/tests/integration/targets/lookup_cartesian/aliases index 2bdcc0113..5e6585203 100644 --- a/ansible_collections/community/general/tests/integration/targets/lookup_cartesian/aliases +++ b/ansible_collections/community/general/tests/integration/targets/lookup_cartesian/aliases @@ -4,4 +4,3 @@ azp/posix/1 skip/aix -skip/python2.6 # lookups are controller only, and we no longer support Python 2.6 on the controller diff --git a/ansible_collections/community/general/tests/integration/targets/lookup_dependent/aliases b/ansible_collections/community/general/tests/integration/targets/lookup_dependent/aliases index 26ad5c244..12d1d6617 100644 --- a/ansible_collections/community/general/tests/integration/targets/lookup_dependent/aliases +++ b/ansible_collections/community/general/tests/integration/targets/lookup_dependent/aliases @@ -3,4 +3,3 @@ # SPDX-License-Identifier: GPL-3.0-or-later azp/posix/2 -skip/python2.6 # lookups are controller only, and we no longer support Python 2.6 on the controller diff --git a/ansible_collections/community/general/tests/integration/targets/lookup_dig/aliases b/ansible_collections/community/general/tests/integration/targets/lookup_dig/aliases index eb449a9cf..afda346c4 100644 --- a/ansible_collections/community/general/tests/integration/targets/lookup_dig/aliases +++ b/ansible_collections/community/general/tests/integration/targets/lookup_dig/aliases @@ -3,4 +3,3 @@ # SPDX-License-Identifier: GPL-3.0-or-later azp/posix/1 -skip/python2.6 # lookups are controller only, and we no longer support Python 2.6 on the controller diff --git a/ansible_collections/community/general/tests/integration/targets/lookup_etcd3/aliases b/ansible_collections/community/general/tests/integration/targets/lookup_etcd3/aliases index b9f3395f7..de1f51cb5 100644 --- a/ansible_collections/community/general/tests/integration/targets/lookup_etcd3/aliases +++ b/ansible_collections/community/general/tests/integration/targets/lookup_etcd3/aliases @@ -10,5 +10,4 @@ skip/aix skip/osx skip/macos skip/freebsd -skip/python2.6 # lookups are controller only, and we no longer support Python 2.6 on the controller disabled # see https://github.com/ansible-collections/community.general/issues/322 diff --git a/ansible_collections/community/general/tests/integration/targets/lookup_flattened/aliases b/ansible_collections/community/general/tests/integration/targets/lookup_flattened/aliases index 0ac9bad98..dadd9f37a 100644 --- a/ansible_collections/community/general/tests/integration/targets/lookup_flattened/aliases +++ b/ansible_collections/community/general/tests/integration/targets/lookup_flattened/aliases @@ -4,4 +4,3 @@ azp/posix/2 skip/aix -skip/python2.6 # lookups are controller only, and we no longer support Python 2.6 on the controller diff --git a/ansible_collections/community/general/tests/integration/targets/lookup_lmdb_kv/aliases b/ansible_collections/community/general/tests/integration/targets/lookup_lmdb_kv/aliases index 66632fb4a..9c7febe24 100644 --- a/ansible_collections/community/general/tests/integration/targets/lookup_lmdb_kv/aliases +++ b/ansible_collections/community/general/tests/integration/targets/lookup_lmdb_kv/aliases @@ -5,4 +5,3 @@ azp/posix/2 destructive skip/aix -skip/python2.6 # lookups are controller only, and we no longer support Python 2.6 on the controller diff --git a/ansible_collections/community/general/tests/integration/targets/lookup_merge_variables/aliases b/ansible_collections/community/general/tests/integration/targets/lookup_merge_variables/aliases index eb449a9cf..afda346c4 100644 --- a/ansible_collections/community/general/tests/integration/targets/lookup_merge_variables/aliases +++ b/ansible_collections/community/general/tests/integration/targets/lookup_merge_variables/aliases @@ -3,4 +3,3 @@ # SPDX-License-Identifier: GPL-3.0-or-later azp/posix/1 -skip/python2.6 # lookups are controller only, and we no longer support Python 2.6 on the controller diff --git a/ansible_collections/community/general/tests/integration/targets/lookup_merge_variables/runme.sh b/ansible_collections/community/general/tests/integration/targets/lookup_merge_variables/runme.sh index 52a38f4a5..4e66476be 100755 --- a/ansible_collections/community/general/tests/integration/targets/lookup_merge_variables/runme.sh +++ b/ansible_collections/community/general/tests/integration/targets/lookup_merge_variables/runme.sh @@ -11,3 +11,6 @@ ANSIBLE_LOG_PATH=/tmp/ansible-test-merge-variables \ ANSIBLE_LOG_PATH=/tmp/ansible-test-merge-variables \ ANSIBLE_MERGE_VARIABLES_PATTERN_TYPE=suffix \ ansible-playbook test_with_env.yml "$@" + +ANSIBLE_LOG_PATH=/tmp/ansible-test-merge-variables \ + ansible-playbook -i test_inventory_all_hosts.yml test_all_hosts.yml "$@" diff --git a/ansible_collections/community/general/tests/integration/targets/lookup_merge_variables/test_all_hosts.yml b/ansible_collections/community/general/tests/integration/targets/lookup_merge_variables/test_all_hosts.yml new file mode 100644 index 000000000..3070087bb --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/lookup_merge_variables/test_all_hosts.yml @@ -0,0 +1,64 @@ +--- +# Copyright (c) 2020, Thales Netherlands +# Copyright (c) 2021, 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: Test merge_variables lookup plugin (multiple hosts) + hosts: host0 + gather_facts: false + tasks: + - name: Test merge dicts via all group + delegate_to: localhost + vars: + merged_dict: "{{ lookup('community.general.merge_variables', '__merge_dict_ex', pattern_type='suffix', groups=['all']) }}" + block: + - name: Test merge dicts via all group - Print the merged dict + ansible.builtin.debug: + msg: "{{ merged_dict }}" + + - name: Test merge dicts via all group - Validate that the dict is complete + ansible.builtin.assert: + that: + - "(merged_dict.keys() | list | length) == 4" + - "'item1' in merged_dict" + - "'item2' in merged_dict" + - "'item3' in merged_dict" + - "'list_item' in merged_dict" + - "merged_dict.list_item | length == 3" + - name: Test merge dicts via two of three groups + delegate_to: localhost + vars: + merged_dict: "{{ lookup('community.general.merge_variables', '__merge_dict_in', pattern_type='suffix', groups=['dummy1', 'dummy2']) }}" + block: + - name: Test merge dicts via two of three groups - Print the merged dict + ansible.builtin.debug: + msg: "{{ merged_dict }}" + + - name: Test merge dicts via two of three groups - Validate that the dict is complete + ansible.builtin.assert: + that: + - "(merged_dict.keys() | list | length) == 3" + - "'item1' in merged_dict" + - "'item2' in merged_dict" + - "'list_item' in merged_dict" + - "merged_dict.list_item | length == 2" + - name: Test merge dicts via two of three groups with inital value + delegate_to: localhost + vars: + initial_dict: + initial: initial_value + merged_dict: "{{ lookup('community.general.merge_variables', '__merge_dict_in', initial_value=initial_dict, pattern_type='suffix', groups=['dummy1', 'dummy2']) }}" + block: + - name: Test merge dicts via two of three groups with inital value - Print the merged dict + ansible.builtin.debug: + msg: "{{ merged_dict }}" + + - name: Test merge dicts via two of three groups with inital value - Validate that the dict is complete + ansible.builtin.assert: + that: + - "(merged_dict.keys() | list | length) == 4" + - "'item1' in merged_dict" + - "'item2' in merged_dict" + - "'list_item' in merged_dict" + - "merged_dict.list_item | length == 2" diff --git a/ansible_collections/community/general/tests/integration/targets/lookup_merge_variables/test_inventory_all_hosts.yml b/ansible_collections/community/general/tests/integration/targets/lookup_merge_variables/test_inventory_all_hosts.yml new file mode 100644 index 000000000..edf5a9e46 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/lookup_merge_variables/test_inventory_all_hosts.yml @@ -0,0 +1,52 @@ +--- +# Copyright (c) 2020, Thales Netherlands +# Copyright (c) 2021, 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 + +all: + hosts: + host0: + host1: + testdict1__merge_dict_ex: + item1: value1 + list_item: + - test1 + + testdict2__merge_dict_ex: + item2: value2 + list_item: + - test2 + + testdict__merge_dict_in: + item1: value1 + list_item: + - test1 + host2: + testdict3__merge_dict_ex: + item3: value3 + list_item: + - test3 + + testdict__merge_dict_in: + item2: value2 + list_item: + - test2 + + host3: + testdict__merge_dict_in: + item3: value3 + list_item: + - test3 + +dummy1: + hosts: + host1: + +dummy2: + hosts: + host2: + +dummy3: + hosts: + host3: diff --git a/ansible_collections/community/general/tests/integration/targets/lookup_passwordstore/aliases b/ansible_collections/community/general/tests/integration/targets/lookup_passwordstore/aliases index 0d4c5af3b..f02225028 100644 --- a/ansible_collections/community/general/tests/integration/targets/lookup_passwordstore/aliases +++ b/ansible_collections/community/general/tests/integration/targets/lookup_passwordstore/aliases @@ -6,6 +6,5 @@ azp/posix/1 destructive skip/aix skip/rhel -skip/python2.6 # lookups are controller only, and we no longer support Python 2.6 on the controller skip/osx # FIXME https://github.com/ansible-collections/community.general/issues/2978 skip/macos # FIXME https://github.com/ansible-collections/community.general/issues/2978 diff --git a/ansible_collections/community/general/tests/integration/targets/lookup_random_pet/aliases b/ansible_collections/community/general/tests/integration/targets/lookup_random_pet/aliases index 0ac9bad98..dadd9f37a 100644 --- a/ansible_collections/community/general/tests/integration/targets/lookup_random_pet/aliases +++ b/ansible_collections/community/general/tests/integration/targets/lookup_random_pet/aliases @@ -4,4 +4,3 @@ azp/posix/2 skip/aix -skip/python2.6 # lookups are controller only, and we no longer support Python 2.6 on the controller diff --git a/ansible_collections/community/general/tests/integration/targets/lookup_random_string/aliases b/ansible_collections/community/general/tests/integration/targets/lookup_random_string/aliases index 0ac9bad98..dadd9f37a 100644 --- a/ansible_collections/community/general/tests/integration/targets/lookup_random_string/aliases +++ b/ansible_collections/community/general/tests/integration/targets/lookup_random_string/aliases @@ -4,4 +4,3 @@ azp/posix/2 skip/aix -skip/python2.6 # lookups are controller only, and we no longer support Python 2.6 on the controller diff --git a/ansible_collections/community/general/tests/integration/targets/lookup_random_words/aliases b/ansible_collections/community/general/tests/integration/targets/lookup_random_words/aliases index 0ac9bad98..dadd9f37a 100644 --- a/ansible_collections/community/general/tests/integration/targets/lookup_random_words/aliases +++ b/ansible_collections/community/general/tests/integration/targets/lookup_random_words/aliases @@ -4,4 +4,3 @@ azp/posix/2 skip/aix -skip/python2.6 # lookups are controller only, and we no longer support Python 2.6 on the controller diff --git a/ansible_collections/community/general/tests/integration/targets/lvg/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/lvg/tasks/main.yml index e14c48c3f..15af2d08c 100644 --- a/ansible_collections/community/general/tests/integration/targets/lvg/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/lvg/tasks/main.yml @@ -18,10 +18,20 @@ block: - import_tasks: setup.yml + - import_tasks: setup_missing_pv.yml + - import_tasks: test_indempotency.yml - import_tasks: test_grow_reduce.yml - import_tasks: test_pvresize.yml + + - import_tasks: test_active_change.yml + + - import_tasks: test_active_create.yml + + - import_tasks: test_uuid_reset.yml always: - import_tasks: teardown.yml + + - import_tasks: teardown_missing_pv.yml diff --git a/ansible_collections/community/general/tests/integration/targets/lvg/tasks/setup.yml b/ansible_collections/community/general/tests/integration/targets/lvg/tasks/setup.yml index 3984b9fc3..45209c6a6 100644 --- a/ansible_collections/community/general/tests/integration/targets/lvg/tasks/setup.yml +++ b/ansible_collections/community/general/tests/integration/targets/lvg/tasks/setup.yml @@ -4,8 +4,8 @@ # SPDX-License-Identifier: GPL-3.0-or-later - name: "Create files to use as a disk devices" - command: "dd if=/dev/zero of={{ remote_tmp_dir }}/img{{ item }} bs=1M count=10" - with_sequence: 'count=2' + command: "dd if=/dev/zero of={{ remote_tmp_dir }}/img{{ item }} bs=1M count=36" + with_sequence: 'count=4' - name: "Show next free loop device" command: "losetup -f" @@ -21,7 +21,23 @@ - name: "Create loop device for file" command: "losetup -f {{ remote_tmp_dir }}/img2" +- name: "Show next free loop device" + command: "losetup -f" + register: loop_device3 + +- name: "Create loop device for file" + command: "losetup -f {{ remote_tmp_dir }}/img3" + +- name: "Show next free loop device" + command: "losetup -f" + register: loop_device4 + +- name: "Create loop device for file" + command: "losetup -f {{ remote_tmp_dir }}/img4" + - name: "Affect name on disk to work on" set_fact: loop_device1: "{{ loop_device1.stdout }}" loop_device2: "{{ loop_device2.stdout }}" + loop_device3: "{{ loop_device3.stdout }}" + loop_device4: "{{ loop_device4.stdout }}" diff --git a/ansible_collections/community/general/tests/integration/targets/lvg/tasks/setup_missing_pv.yml b/ansible_collections/community/general/tests/integration/targets/lvg/tasks/setup_missing_pv.yml new file mode 100644 index 000000000..863ef8757 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/lvg/tasks/setup_missing_pv.yml @@ -0,0 +1,18 @@ +--- +# 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: "Prepare VG for missing PV" + lvg: + vg: vg_with_missing_pv + pvs: + - "{{ loop_device3 }}" + - "{{ loop_device4 }}" + +- name: Save loop_device4 pvid + shell: "pvs -ouuid --noheadings {{ loop_device4 }} | xargs -n1 | tr -d '-'" + register: loop_device4_pvid_result + +- name: Detach loop_device4 + command: "losetup -d {{ loop_device4 }}" diff --git a/ansible_collections/community/general/tests/integration/targets/lvg/tasks/teardown.yml b/ansible_collections/community/general/tests/integration/targets/lvg/tasks/teardown.yml index de4957321..2d147dee0 100644 --- a/ansible_collections/community/general/tests/integration/targets/lvg/tasks/teardown.yml +++ b/ansible_collections/community/general/tests/integration/targets/lvg/tasks/teardown.yml @@ -6,13 +6,25 @@ - name: Remove test volume group lvg: vg: testvg + force: true state: absent +- name: Remove LVM devices + loop: + - "{{ loop_device1 | default('') }}" + - "{{ loop_device2 | default('') }}" + - "{{ loop_device3 | default('') }}" + when: + - item|length > 0 + command: "lvmdevices --deldev {{ item }}" + ignore_errors: true + - name: Detach loop devices command: "losetup -d {{ item }}" loop: - "{{ loop_device1 | default('') }}" - "{{ loop_device2 | default('') }}" + - "{{ loop_device3 | default('') }}" when: - item != '' @@ -20,4 +32,4 @@ file: path: "{{ remote_tmp_dir }}/img{{ item }}" state: absent - with_sequence: 'count=2' + with_sequence: 'count=4' diff --git a/ansible_collections/community/general/tests/integration/targets/lvg/tasks/teardown_missing_pv.yml b/ansible_collections/community/general/tests/integration/targets/lvg/tasks/teardown_missing_pv.yml new file mode 100644 index 000000000..4cff68003 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/lvg/tasks/teardown_missing_pv.yml @@ -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 + +- name: Remove loop_device4 LVM device + command: "lvmdevices --delpvid {{ loop_device4_pvid_result.stdout }}" + ignore_errors: true diff --git a/ansible_collections/community/general/tests/integration/targets/lvg/tasks/test_active_change.yml b/ansible_collections/community/general/tests/integration/targets/lvg/tasks/test_active_change.yml new file mode 100644 index 000000000..7df52683f --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/lvg/tasks/test_active_change.yml @@ -0,0 +1,163 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Create volume group on disk device + lvg: + vg: testvg + pvs: "{{ loop_device1 }}" + +- name: Create logical volumes on volume group + loop: + - lv1 + - lv2 + lvol: + vg: testvg + lv: "{{ item }}" + size: 2m + +- name: Create snapshot volumes of origin logical volumes + loop: + - lv1 + - lv2 + lvol: + vg: testvg + lv: "{{ item }}" + snapshot: "{{ item }}_snap" + size: 50%ORIGIN + +- name: Collect all lv active status in testvg + shell: vgs -olv_active --noheadings testvg | xargs -n1 + register: initial_lv_status_result + +- name: Assert all lv in testvg are active + loop: "{{ initial_lv_status_result.stdout_lines }}" + assert: + that: + - "'active' == item" + +- name: Deactivate volume group + lvg: + state: inactive + vg: testvg + register: vg_deactivate_result + +- name: Collect all lv active status in testvg + shell: vgs -olv_active --noheadings testvg | xargs -n1 + register: deactivated_lv_status_result + +- name: Do all assertions to verify expected results + assert: + that: + - vg_deactivate_result is changed + - "'active' not in deactivated_lv_status_result.stdout" + +- name: Deactivate volume group again to verify idempotence + lvg: + state: inactive + vg: testvg + register: repeated_vg_deactivate_result + +- name: Verify vg deactivation idempontency + assert: + that: + - repeated_vg_deactivate_result is not changed + +- name: Activate volume group in check mode + lvg: + state: active + vg: testvg + register: check_mode_vg_activate_result + check_mode: true + +- name: Collect all lv active status in testvg + shell: vgs -olv_active --noheadings testvg | xargs -n1 + register: check_mode_activate_lv_status_result + +- name: Verify VG activation in check mode changed without activating LVs + assert: + that: + - check_mode_vg_activate_result is changed + - "'active' not in check_mode_activate_lv_status_result.stdout" + +- name: Activate volume group + lvg: + state: active + vg: testvg + register: vg_activate_result + +- name: Collect all lv active status in testvg + shell: vgs -olv_active --noheadings testvg | xargs -n1 + register: activate_lv_status_result + +- name: Verify vg activation + assert: + that: + - vg_activate_result is changed + +- name: Assert all lv in testvg are active + loop: "{{ activate_lv_status_result.stdout_lines }}" + assert: + that: + - "'active' == item" + +- name: Activate volume group again to verify idempontency + lvg: + state: active + vg: testvg + register: repeated_vg_activate_result + +- name: Verify vg activation idempontency + assert: + that: + - repeated_vg_activate_result is not changed + +- name: Deactivate lv2 in testvg + lvol: + vg: testvg + lv: lv2 + active: false + +- name: Activate volume group again to verify partially activated vg activation + lvg: + state: active + vg: testvg + register: partial_vg_activate_result + +- name: Verify partially activated vg activation + assert: + that: + - partial_vg_activate_result is changed + +- name: Collect all lv active status in testvg + shell: vgs -olv_active --noheadings testvg | xargs -n1 + register: activate_partial_lv_status_result + +- name: Assert all lv in testvg are active + loop: "{{ activate_partial_lv_status_result.stdout_lines }}" + assert: + that: + - "'active' == item" + +- name: Deactivate volume group in check mode + lvg: + state: inactive + vg: testvg + register: check_mode_vg_deactivate_result + check_mode: true + +- name: Collect all lv active status in testvg + shell: vgs -olv_active --noheadings testvg | xargs -n1 + register: check_mode_deactivate_lv_status_result + +- name: Verify check mode vg deactivation changed + assert: + that: + - check_mode_vg_deactivate_result is changed + +- name: Assert all lv in testvg are still active + loop: "{{ check_mode_deactivate_lv_status_result.stdout_lines }}" + assert: + that: + - "'active' == item" diff --git a/ansible_collections/community/general/tests/integration/targets/lvg/tasks/test_active_create.yml b/ansible_collections/community/general/tests/integration/targets/lvg/tasks/test_active_create.yml new file mode 100644 index 000000000..7ac1ffedd --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/lvg/tasks/test_active_create.yml @@ -0,0 +1,71 @@ +--- +# 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: Collect vgcreate help + command: "vgcreate --help" + register: vgcreate_help_result + +- when: "'--setautoactivation' in vgcreate_help_result.stdout" + block: + - name: Create autoactivated volume group on disk device + lvg: + state: active + vg: vg_autoact_test + pvs: "{{ loop_device2 }}" + + - name: Collect vg autoactivation status for vg_autoact_test + shell: vgs -oautoactivation --noheadings vg_autoact_test | xargs -n1 + register: active_vg_autoact_status_result + + - name: Assert vg autoactivation is set for vg_autoact_test + assert: + that: "'enabled' == active_vg_autoact_status_result.stdout" + + - name: Remove vg_autoact_test for the next test + lvg: + state: absent + vg: vg_autoact_test + force: true + + - name: Create auttoactivation disabled volume group on disk device + lvg: + state: inactive + vg: vg_autoact_test + pvs: "{{ loop_device2 }}" + + - name: Collect vg autoactivation status for vg_autoact_test + shell: vgs -oautoactivation --noheadings vg_autoact_test | xargs -n1 + register: inactive_vg_autoact_status_result + + - name: Assert vg autoactivation disabled for vg_autoact_test + assert: + that: "inactive_vg_autoact_status_result.stdout | length == 0" + + - name: Remove vg_autoact_test for the next test + lvg: + state: absent + vg: vg_autoact_test + force: true + + - name: Create auttoactivation disabled by option volume group on disk device + lvg: + state: active + vg: vg_autoact_test + vg_options: "--setautoactivation n" + pvs: "{{ loop_device2 }}" + + - name: Collect vg autoactivation status for vg_autoact_test + shell: vgs -oautoactivation --noheadings vg_autoact_test | xargs -n1 + register: inactive_by_option_vg_autoact_status_result + + - name: Assert vg autoactivation disabled by option for vg_autoact_test + assert: + that: "inactive_by_option_vg_autoact_status_result.stdout | length == 0" + always: + - name: Cleanup vg_autoact_test + lvg: + state: absent + vg: vg_autoact_test + force: true diff --git a/ansible_collections/community/general/tests/integration/targets/lvg/tasks/test_pvresize.yml b/ansible_collections/community/general/tests/integration/targets/lvg/tasks/test_pvresize.yml index f15add91c..3f3b9dbdd 100644 --- a/ansible_collections/community/general/tests/integration/targets/lvg/tasks/test_pvresize.yml +++ b/ansible_collections/community/general/tests/integration/targets/lvg/tasks/test_pvresize.yml @@ -12,10 +12,10 @@ shell: vgs -v testvg -o pv_size --noheading --units b | xargs register: cmd_result -- name: Assert the testvg size is 8388608B +- name: Assert the testvg size is 33554432B assert: that: - - "'8388608B' == cmd_result.stdout" + - "'33554432B' == cmd_result.stdout" - name: Increases size in file command: "dd if=/dev/zero bs=8MiB count=1 of={{ remote_tmp_dir }}/img1 conv=notrunc oflag=append" @@ -38,10 +38,10 @@ shell: vgs -v testvg -o pv_size --noheading --units b | xargs register: cmd_result -- name: Assert the testvg size is still 8388608B +- name: Assert the testvg size is still 33554432B assert: that: - - "'8388608B' == cmd_result.stdout" + - "'33554432B' == cmd_result.stdout" - name: "Reruns lvg with pvresize:yes and check_mode:yes" lvg: @@ -60,10 +60,10 @@ shell: vgs -v testvg -o pv_size --noheading --units b | xargs register: cmd_result -- name: Assert the testvg size is still 8388608B +- name: Assert the testvg size is still 33554432B assert: that: - - "'8388608B' == cmd_result.stdout" + - "'33554432B' == cmd_result.stdout" - name: "Reruns lvg with pvresize:yes" lvg: @@ -75,7 +75,7 @@ shell: vgs -v testvg -o pv_size --noheading --units b | xargs register: cmd_result -- name: Assert the testvg size is now 16777216B +- name: Assert the testvg size is now 41943040B assert: that: - - "'16777216B' == cmd_result.stdout" + - "'41943040B' == cmd_result.stdout" diff --git a/ansible_collections/community/general/tests/integration/targets/lvg/tasks/test_uuid_reset.yml b/ansible_collections/community/general/tests/integration/targets/lvg/tasks/test_uuid_reset.yml new file mode 100644 index 000000000..8de50ace5 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/lvg/tasks/test_uuid_reset.yml @@ -0,0 +1,107 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Create volume group on disk device + lvg: + vg: testvg + pvs: "{{ loop_device1 }}" + +- name: Save testvg uuid + shell: vgs -ouuid --noheadings testvg | xargs -n1 + register: orig_vg_uuid_cmd_result + +- name: Save pv uuid + shell: "pvs -ouuid --noheadings {{ loop_device1 }} | xargs -n1" + register: orig_pv_uuid_cmd_result + +- name: Deactivate and reset vg/pv uuid + lvg: + state: inactive + vg: testvg + pvs: "{{ loop_device1 }}" + reset_vg_uuid: true + reset_pv_uuid: true + register: vg_uuid_reset + +- name: Save testvg uuid + shell: vgs -ouuid --noheadings testvg | xargs -n1 + register: new_vg_uuid_cmd_result + +- name: Save pv uuid + shell: "pvs -ouuid --noheadings {{ loop_device1 }} | xargs -n1" + register: new_pv_uuid_cmd_result + +- name: Do all assertions to verify expected results + assert: + that: + - vg_uuid_reset is changed + - orig_vg_uuid_cmd_result.stdout != new_vg_uuid_cmd_result.stdout + - orig_pv_uuid_cmd_result.stdout != new_pv_uuid_cmd_result.stdout + +- name: Reset vg uuid again to verify non-idempotence + lvg: + vg: testvg + reset_vg_uuid: true + register: repeat_vg_uuid_reset + +- name: Reset pv uuid again to verify non-idempotence + lvg: + vg: testvg + reset_pv_uuid: true + pvs: "{{ loop_device1 }}" + register: repeat_pv_uuid_reset + +- name: Save testvg uuid + shell: vgs -ouuid --noheadings testvg | xargs -n1 + register: repeat_vg_uuid_cmd_result + +- name: Save pv uuid + shell: "pvs -ouuid --noheadings {{ loop_device1 }} | xargs -n1" + register: repeat_pv_uuid_cmd_result + +- name: Do all assertions to verify expected results + assert: + that: + - repeat_vg_uuid_reset is changed + - repeat_pv_uuid_reset is changed + - new_vg_uuid_cmd_result.stdout != repeat_vg_uuid_cmd_result.stdout + - new_pv_uuid_cmd_result.stdout != repeat_pv_uuid_cmd_result.stdout + +- name: Reset vg uuid in check mode + lvg: + vg: testvg + reset_vg_uuid: true + register: check_mode_vg_uuid_reset + check_mode: true + +- name: Reset pv uuid in check mode + lvg: + vg: testvg + reset_pv_uuid: true + pvs: "{{ loop_device1 }}" + register: check_mode_pv_uuid_reset + check_mode: true + +- name: Save testvg uuid + shell: vgs -ouuid --noheadings testvg | xargs -n1 + register: check_mode_vg_uuid_cmd_result + +- name: Save pv uuid + shell: "pvs -ouuid --noheadings {{ loop_device1 }} | xargs -n1" + register: check_mode_pv_uuid_cmd_result + +- name: Do all assertions to verify expected results + assert: + that: + - check_mode_vg_uuid_reset is changed + - check_mode_pv_uuid_reset is changed + - check_mode_vg_uuid_cmd_result.stdout == repeat_vg_uuid_cmd_result.stdout + - check_mode_pv_uuid_cmd_result.stdout == repeat_pv_uuid_cmd_result.stdout + +- name: Activate volume group + lvg: + state: active + vg: testvg + register: vg_activate diff --git a/ansible_collections/community/general/tests/integration/targets/lvg_rename/aliases b/ansible_collections/community/general/tests/integration/targets/lvg_rename/aliases new file mode 100644 index 000000000..64d439099 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/lvg_rename/aliases @@ -0,0 +1,13 @@ +# Copyright (c) Contributors to the Ansible project +# Based on the integraton test for the lvg module +# 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/1 +azp/posix/vm +destructive +needs/privileged +skip/aix +skip/freebsd +skip/osx +skip/macos diff --git a/ansible_collections/community/general/tests/integration/targets/lvg_rename/meta/main.yml b/ansible_collections/community/general/tests/integration/targets/lvg_rename/meta/main.yml new file mode 100644 index 000000000..90c5d5cb8 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/lvg_rename/meta/main.yml @@ -0,0 +1,9 @@ +--- +# Copyright (c) Ansible Project +# Based on the integraton test for the lvg module +# 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_remote_tmp_dir diff --git a/ansible_collections/community/general/tests/integration/targets/lvg_rename/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/lvg_rename/tasks/main.yml new file mode 100644 index 000000000..18dd6f1ba --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/lvg_rename/tasks/main.yml @@ -0,0 +1,25 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Copyright (c) Contributors to the Ansible project +# Based on the integraton test for the lvg module +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install required packages (Linux) + when: ansible_system == 'Linux' + ansible.builtin.package: + name: lvm2 + state: present + +- name: Test lvg_rename module + block: + - import_tasks: setup.yml + + - import_tasks: test.yml + + always: + - import_tasks: teardown.yml diff --git a/ansible_collections/community/general/tests/integration/targets/lvg_rename/tasks/setup.yml b/ansible_collections/community/general/tests/integration/targets/lvg_rename/tasks/setup.yml new file mode 100644 index 000000000..01721e42d --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/lvg_rename/tasks/setup.yml @@ -0,0 +1,50 @@ +--- +# Copyright (c) Contributors to the Ansible project +# Based on the integraton test for the lvg module +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Create files to use as disk devices + with_sequence: 'count=2' + ansible.builtin.command: + cmd: "dd if=/dev/zero of={{ remote_tmp_dir }}/img{{ item }} bs=1M count=10" + creates: "{{ remote_tmp_dir }}/img{{ item }}" + +- name: Show next free loop device + ansible.builtin.command: + cmd: "losetup -f" + changed_when: false + register: loop_device1 + +- name: "Create loop device for file {{ remote_tmp_dir }}/img1" + ansible.builtin.command: + cmd: "losetup -f {{ remote_tmp_dir }}/img1" + changed_when: true + +- name: Show next free loop device + ansible.builtin.command: + cmd: "losetup -f" + changed_when: false + register: loop_device2 + +- name: "Create loop device for file {{ remote_tmp_dir }}/img2" + ansible.builtin.command: + cmd: "losetup -f {{ remote_tmp_dir }}/img2" + changed_when: true + +- name: Affect name on disk to work on + ansible.builtin.set_fact: + loop_device1: "{{ loop_device1.stdout }}" + loop_device2: "{{ loop_device2.stdout }}" + +- name: "Create test volume group testvg on {{ loop_device1 }}" + community.general.lvg: + vg: "testvg" + state: present + pvs: "{{ loop_device1 }}" + +- name: "Create test volume group testvg2 on {{ loop_device2 }}" + community.general.lvg: + vg: "testvg2" + state: present + pvs: "{{ loop_device2 }}" diff --git a/ansible_collections/community/general/tests/integration/targets/lvg_rename/tasks/teardown.yml b/ansible_collections/community/general/tests/integration/targets/lvg_rename/tasks/teardown.yml new file mode 100644 index 000000000..71c33d56d --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/lvg_rename/tasks/teardown.yml @@ -0,0 +1,46 @@ +--- +# Copyright (c) Contributors to the Ansible project +# Based on the integraton test for the lvg module +# 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: Collect test volume groups + ansible.builtin.command: + cmd: "pvs --noheadings -ovg_name {{ loop_device1 | default('') }} {{ loop_device2 | default('') }}" + register: test_vgs_output + changed_when: false + +- name: Remove test volume groups + loop: "{{ test_vgs_output.stdout_lines }}" + loop_control: + label: "{{ item | trim }}" + community.general.lvg: + vg: "{{ item | trim }}" + state: absent + +- name: Remove lvmdevices + loop: + - "{{ loop_device1 | default('') }}" + - "{{ loop_device2 | default('') }}" + when: + - item | length > 0 + ansible.builtin.command: + cmd: "lvmdevices --deldev {{ item }}" + failed_when: false + changed_when: true + +- name: Detach loop devices + loop: + - "{{ loop_device1 | default('') }}" + - "{{ loop_device2 | default('') }}" + when: + - item | length > 0 + ansible.builtin.command: + cmd: "losetup -d {{ item }}" + changed_when: true + +- name: Remove device files + with_sequence: 'count=2' + ansible.builtin.file: + path: "{{ remote_tmp_dir }}/img{{ item }}" + state: absent diff --git a/ansible_collections/community/general/tests/integration/targets/lvg_rename/tasks/test.yml b/ansible_collections/community/general/tests/integration/targets/lvg_rename/tasks/test.yml new file mode 100644 index 000000000..ab62b679b --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/lvg_rename/tasks/test.yml @@ -0,0 +1,105 @@ +--- +# Copyright (c) Contributors to the 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: Rename a VG in check mode + community.general.lvg_rename: + vg: testvg + vg_new: testvg_renamed + check_mode: true + register: check_mode_vg_rename + +- name: Check if testvg still exists + ansible.builtin.command: + cmd: vgs testvg + changed_when: false + +- name: Assert that renaming a VG is changed - check mode + assert: + that: + - check_mode_vg_rename is changed + +- name: Rename testvg to testvg_renamed + community.general.lvg_rename: + vg: testvg + vg_new: testvg_renamed + register: vg_renamed + +- name: Assert that renaming a VG is changed + assert: + that: + - vg_renamed is changed + +- name: Check if testvg does not exists + ansible.builtin.command: + cmd: vgs testvg + register: check_testvg_existence_result + failed_when: check_testvg_existence_result.rc == 0 + changed_when: false + +- name: Check if testvg_renamed exists + ansible.builtin.command: + cmd: vgs testvg_renamed + changed_when: false + +- name: Rename testvg to testvg_renamed again for testing idempotency - check mode + community.general.lvg_rename: + vg: testvg + vg_new: testvg_renamed + check_mode: true + register: check_mode_vg_renamed_again + +- name: Rename testvg to testvg_renamed again for testing idempotency + community.general.lvg_rename: + vg: testvg + vg_new: testvg_renamed + register: vg_renamed_again + +- name: Assert that renaming a VG again is not changed + assert: + that: + - check_mode_vg_renamed_again is not changed + - vg_renamed_again is not changed + +- name: Rename a non-existing VG - check mode + community.general.lvg_rename: + vg: testvg + vg_new: testvg_ne + check_mode: true + ignore_errors: true + register: check_mode_ne_vg_rename + +- name: Rename a non-existing VG + community.general.lvg_rename: + vg: testvg + vg_new: testvg_ne + ignore_errors: true + register: ne_vg_rename + +- name: Assert that renaming a no-existing VG failed + assert: + that: + - check_mode_ne_vg_rename is failed + - ne_vg_rename is failed + +- name: Rename testvg_renamed to the existing testvg2 name - check mode + community.general.lvg_rename: + vg: testvg_renamed + vg_new: testvg2 + check_mode: true + ignore_errors: true + register: check_mode_vg_rename_collision + +- name: Rename testvg_renamed to the existing testvg2 name + community.general.lvg_rename: + vg: testvg_renamed + vg_new: testvg2 + ignore_errors: true + register: vg_rename_collision + +- name: Assert that renaming to an existing VG name failed + assert: + that: + - check_mode_vg_rename_collision is failed + - vg_rename_collision is failed diff --git a/ansible_collections/community/general/tests/integration/targets/lvol/aliases b/ansible_collections/community/general/tests/integration/targets/lvol/aliases new file mode 100644 index 000000000..3b92ba75c --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/lvol/aliases @@ -0,0 +1,12 @@ +# 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/1 +azp/posix/vm +destructive +needs/privileged +skip/aix +skip/freebsd +skip/osx +skip/macos diff --git a/ansible_collections/community/general/tests/integration/targets/lvol/meta/main.yml b/ansible_collections/community/general/tests/integration/targets/lvol/meta/main.yml new file mode 100644 index 000000000..ca1915e05 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/lvol/meta/main.yml @@ -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 + +dependencies: + - setup_pkg_mgr + - setup_remote_tmp_dir diff --git a/ansible_collections/community/general/tests/integration/targets/lvol/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/lvol/tasks/main.yml new file mode 100644 index 000000000..0e3bfa1a3 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/lvol/tasks/main.yml @@ -0,0 +1,24 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install required packages (Linux) + package: + name: lvm2 + state: present + when: ansible_system == 'Linux' + +- name: Test lvol module + block: + - import_tasks: setup.yml + + - import_tasks: test_pvs.yml + + always: + - import_tasks: teardown.yml diff --git a/ansible_collections/community/general/tests/integration/targets/lvol/tasks/setup.yml b/ansible_collections/community/general/tests/integration/targets/lvol/tasks/setup.yml new file mode 100644 index 000000000..195c50111 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/lvol/tasks/setup.yml @@ -0,0 +1,57 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: "Create files to use as a disk devices" + command: "dd if=/dev/zero of={{ remote_tmp_dir }}/img{{ item }} bs=1M count=36" + with_sequence: 'count=4' + +- name: "Show next free loop device" + command: "losetup -f" + register: loop_device1 + +- name: "Create loop device for file" + command: "losetup -f {{ remote_tmp_dir }}/img1" + +- name: "Show next free loop device" + command: "losetup -f" + register: loop_device2 + +- name: "Create loop device for file" + command: "losetup -f {{ remote_tmp_dir }}/img2" + +- name: "Show next free loop device" + command: "losetup -f" + register: loop_device3 + +- name: "Create loop device for file" + command: "losetup -f {{ remote_tmp_dir }}/img3" + +- name: "Show next free loop device" + command: "losetup -f" + register: loop_device4 + +- name: "Create loop device for file" + command: "losetup -f {{ remote_tmp_dir }}/img4" + +- name: "Set loop device names" + set_fact: + loop_device1: "{{ loop_device1.stdout }}" + loop_device2: "{{ loop_device2.stdout }}" + loop_device3: "{{ loop_device3.stdout }}" + loop_device4: "{{ loop_device4.stdout }}" + +- name: Create testvg1 volume group on disk devices + lvg: + vg: testvg1 + pvs: + - "{{ loop_device1 }}" + - "{{ loop_device2 }}" + +- name: Create testvg2 volume group on disk devices + lvg: + vg: testvg2 + pvs: + - "{{ loop_device3 }}" + - "{{ loop_device4 }}" diff --git a/ansible_collections/community/general/tests/integration/targets/lvol/tasks/teardown.yml b/ansible_collections/community/general/tests/integration/targets/lvol/tasks/teardown.yml new file mode 100644 index 000000000..a8080f874 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/lvol/tasks/teardown.yml @@ -0,0 +1,40 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Remove test volume groups + loop: + - testvg1 + - testvg2 + lvg: + vg: "{{ item }}" + force: true + state: absent + +- name: Remove LVM devices + loop: + - "{{ loop_device1 | default('') }}" + - "{{ loop_device2 | default('') }}" + - "{{ loop_device3 | default('') }}" + - "{{ loop_device4 | default('') }}" + when: + - item|length > 0 + command: "lvmdevices --deldev {{ item }}" + ignore_errors: true + +- name: Detach loop devices + command: "losetup -d {{ item }}" + loop: + - "{{ loop_device1 | default('') }}" + - "{{ loop_device2 | default('') }}" + - "{{ loop_device3 | default('') }}" + - "{{ loop_device4 | default('') }}" + when: + - item != '' + +- name: Remove device files + file: + path: "{{ remote_tmp_dir }}/img{{ item }}" + state: absent + with_sequence: 'count=4' diff --git a/ansible_collections/community/general/tests/integration/targets/lvol/tasks/test_pvs.yml b/ansible_collections/community/general/tests/integration/targets/lvol/tasks/test_pvs.yml new file mode 100644 index 000000000..c1cd3d1a8 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/lvol/tasks/test_pvs.yml @@ -0,0 +1,64 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Create logical volume (testlv1) with pvs set as comma separated string + lvol: + vg: testvg1 + lv: testlv1 + size: 50%PVS + pvs: "{{ loop_device1 }},{{ loop_device2 }}" + register: css_pvs_create_testlv1_result + +- name: Assert logical volume (testlv1) created with pvs set as comma separated string + assert: + that: + - css_pvs_create_testlv1_result is success + - css_pvs_create_testlv1_result is changed + +- name: Create logical volume (testlv1) with pvs set as list + lvol: + vg: testvg1 + lv: testlv1 + size: 50%PVS + pvs: + - "{{ loop_device1 }}" + - "{{ loop_device2 }}" + register: list_pvs_create_testlv1_result + +- name: Assert logical volume (testlv1) creation idempotency with pvs set as list on second execution + assert: + that: + - list_pvs_create_testlv1_result is success + - list_pvs_create_testlv1_result is not changed + +- name: Create logical volume (testlv2) with pvs set as list + lvol: + vg: testvg2 + lv: testlv2 + size: 50%PVS + pvs: + - "{{ loop_device3 }}" + - "{{ loop_device4 }}" + register: list_pvs_create_testlv2_result + +- name: Assert logical volume (testlv2) created with pvs set as list + assert: + that: + - list_pvs_create_testlv2_result is success + - list_pvs_create_testlv2_result is changed + +- name: Create logical volume (testlv2) with pvs set as comma separated string + lvol: + vg: testvg2 + lv: testlv2 + size: 50%PVS + pvs: "{{ loop_device3 }},{{ loop_device4 }}" + register: css_pvs_create_testlv2_result + +- name: Assert logical volume (testlv2) creation idempotency with pvs set as comma separated string on second execution + assert: + that: + - css_pvs_create_testlv2_result is success + - css_pvs_create_testlv2_result is not changed diff --git a/ansible_collections/community/general/tests/integration/targets/mail/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/mail/tasks/main.yml index 4f3f90a51..83c242ad2 100644 --- a/ansible_collections/community/general/tests/integration/targets/mail/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/mail/tasks/main.yml @@ -10,96 +10,101 @@ # TODO: Our current implementation does not handle SMTP authentication -# NOTE: If the system does not support smtpd-tls (python 2.6 and older) we do basic tests -- name: Attempt to install smtpd-tls - pip: - name: smtpd-tls - state: present - ignore_errors: true - register: smtpd_tls +- when: + # TODO: https://github.com/ansible-collections/community.general/issues/4656 + - ansible_python.version.major != 3 or ansible_python.version.minor < 12 + block: -- name: Install test smtpserver - copy: - src: '{{ item }}' - dest: '{{ remote_tmp_dir }}/{{ item }}' - loop: - - smtpserver.py - - smtpserver.crt - - smtpserver.key + # NOTE: If the system does not support smtpd-tls (python 2.6 and older) we do basic tests + - name: Attempt to install smtpd-tls + pip: + name: smtpd-tls + state: present + ignore_errors: true + register: smtpd_tls -# FIXME: Verify the mail after it was send would be nice -# This would require either dumping the content, or registering async task output -- name: Start test smtpserver - shell: '{{ ansible_python.executable }} {{ remote_tmp_dir }}/smtpserver.py 10025:10465' - async: 45 - poll: 0 - register: smtpserver + - name: Install test smtpserver + copy: + src: '{{ item }}' + dest: '{{ remote_tmp_dir }}/{{ item }}' + loop: + - smtpserver.py + - smtpserver.crt + - smtpserver.key -- name: Send a basic test-mail - mail: - port: 10025 - subject: Test mail 1 (smtp) - secure: never + # FIXME: Verify the mail after it was send would be nice + # This would require either dumping the content, or registering async task output + - name: Start test smtpserver + shell: '{{ ansible_python.executable }} {{ remote_tmp_dir }}/smtpserver.py 10025:10465' + async: 45 + poll: 0 + register: smtpserver -- name: Send a test-mail with body and specific recipient - mail: - port: 10025 - from: ansible@localhost - to: root@localhost - subject: Test mail 2 (smtp + body) - body: Test body 2 - secure: never + - name: Send a basic test-mail + mail: + port: 10025 + subject: Test mail 1 (smtp) + secure: never -- name: Send a test-mail with attachment - mail: - port: 10025 - from: ansible@localhost - to: root@localhost - subject: Test mail 3 (smtp + body + attachment) - body: Test body 3 - attach: /etc/group - secure: never + - name: Send a test-mail with body and specific recipient + mail: + port: 10025 + from: ansible@localhost + to: root@localhost + subject: Test mail 2 (smtp + body) + body: Test body 2 + secure: never -# NOTE: This might fail if smtpd-tls is missing or python 2.7.8 or older is used -- name: Send a test-mail using starttls - mail: - port: 10025 - from: ansible@localhost - to: root@localhost - subject: Test mail 4 (smtp + starttls + body + attachment) - body: Test body 4 - attach: /etc/group - secure: starttls - ignore_errors: true - register: starttls_support + - name: Send a test-mail with attachment + mail: + port: 10025 + from: ansible@localhost + to: root@localhost + subject: Test mail 3 (smtp + body + attachment) + body: Test body 3 + attach: /etc/group + secure: never -# NOTE: This might fail if smtpd-tls is missing or python 2.7.8 or older is used -- name: Send a test-mail using TLS - mail: - port: 10465 - from: ansible@localhost - to: root@localhost - subject: Test mail 5 (smtp + tls + body + attachment) - body: Test body 5 - attach: /etc/group - secure: always - ignore_errors: true - register: tls_support + # NOTE: This might fail if smtpd-tls is missing or python 2.7.8 or older is used + - name: Send a test-mail using starttls + mail: + port: 10025 + from: ansible@localhost + to: root@localhost + subject: Test mail 4 (smtp + starttls + body + attachment) + body: Test body 4 + attach: /etc/group + secure: starttls + ignore_errors: true + register: starttls_support -- fail: - msg: Sending mail using starttls failed. - when: smtpd_tls is succeeded and starttls_support is failed and tls_support is succeeded + # NOTE: This might fail if smtpd-tls is missing or python 2.7.8 or older is used + - name: Send a test-mail using TLS + mail: + port: 10465 + from: ansible@localhost + to: root@localhost + subject: Test mail 5 (smtp + tls + body + attachment) + body: Test body 5 + attach: /etc/group + secure: always + ignore_errors: true + register: tls_support -- fail: - msg: Send mail using TLS failed. - when: smtpd_tls is succeeded and tls_support is failed and starttls_support is succeeded + - fail: + msg: Sending mail using starttls failed. + when: smtpd_tls is succeeded and starttls_support is failed and tls_support is succeeded -- name: Send a test-mail with body, specific recipient and specific ehlohost - mail: - port: 10025 - ehlohost: some.domain.tld - from: ansible@localhost - to: root@localhost - subject: Test mail 6 (smtp + body + ehlohost) - body: Test body 6 - secure: never + - fail: + msg: Send mail using TLS failed. + when: smtpd_tls is succeeded and tls_support is failed and starttls_support is succeeded + + - name: Send a test-mail with body, specific recipient and specific ehlohost + mail: + port: 10025 + ehlohost: some.domain.tld + from: ansible@localhost + to: root@localhost + subject: Test mail 6 (smtp + body + ehlohost) + body: Test body 6 + secure: never diff --git a/ansible_collections/community/general/tests/integration/targets/mas/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/mas/tasks/main.yml index f659160dc..839620779 100644 --- a/ansible_collections/community/general/tests/integration/targets/mas/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/mas/tasks/main.yml @@ -117,7 +117,7 @@ that: - install_status.stat.exists == true -- name: Unistall Rested +- name: Uninstall Rested mas: id: 421879749 state: absent diff --git a/ansible_collections/community/general/tests/integration/targets/monit/aliases b/ansible_collections/community/general/tests/integration/targets/monit/aliases index ca39d1353..c78104339 100644 --- a/ansible_collections/community/general/tests/integration/targets/monit/aliases +++ b/ansible_collections/community/general/tests/integration/targets/monit/aliases @@ -9,6 +9,5 @@ skip/osx skip/macos skip/freebsd skip/aix -skip/python2.6 # python-daemon package used in integration tests requires >=2.7 skip/rhel # FIXME unstable # TODO: the tests fail a lot; 'unstable' only requires them to pass when the module itself has been modified diff --git a/ansible_collections/community/general/tests/integration/targets/mssql_script/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/mssql_script/tasks/main.yml index 6fa4d3501..481522216 100644 --- a/ansible_collections/community/general/tests/integration/targets/mssql_script/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/mssql_script/tasks/main.yml @@ -49,6 +49,19 @@ login_port: "{{ mssql_port }}" script: "SELECT 1" +- name: Execute a malformed query + community.general.mssql_script: + login_user: "{{ mssql_login_user }}" + login_password: "{{ mssql_login_password }}" + login_host: "{{ mssql_host }}" + login_port: "{{ mssql_port }}" + script: "SELCT 1" + failed_when: false + register: bad_query +- assert: + that: + - bad_query.error.startswith('ProgrammingError') + - name: two batches with default output community.general.mssql_script: login_user: "{{ mssql_login_user }}" @@ -135,6 +148,30 @@ - result_batches_dict.query_results_dict[0][0] | length == 1 # one row in first select - result_batches_dict.query_results_dict[0][0][0]['b0s0'] == 'Batch 0 - Select 0' # column 'b0s0' of first row +- name: Multiple batches with no resultsets and mixed-case GO + community.general.mssql_script: + login_user: "{{ mssql_login_user }}" + login_password: "{{ mssql_login_password }}" + login_host: "{{ mssql_host }}" + login_port: "{{ mssql_port }}" + script: | + CREATE TABLE #integration56yH2 (c1 VARCHAR(10), c2 VARCHAR(10)) + Go + INSERT INTO #integration56yH2 VALUES ('C1_VALUE1', 'C2_VALUE1') + gO + UPDATE #integration56yH2 SET c2 = 'C2_VALUE2' WHERE c1 = 'C1_VALUE1' + go + SELECT * from #integration56yH2 + GO + DROP TABLE #integration56yH2 + register: empty_batches +- assert: + that: + - empty_batches.query_results | length == 5 # five batch results + - empty_batches.query_results[3][0] | length == 1 # one row in select + - empty_batches.query_results[3][0][0] | length == 2 # two columns in row + - empty_batches.query_results[3][0][0][1] == 'C2_VALUE2' # value has been updated + - name: Stored procedure may return multiple result sets community.general.mssql_script: login_user: "{{ mssql_login_user }}" diff --git a/ansible_collections/community/general/tests/integration/targets/nomad/aliases b/ansible_collections/community/general/tests/integration/targets/nomad/aliases index ad2435c82..5886d4799 100644 --- a/ansible_collections/community/general/tests/integration/targets/nomad/aliases +++ b/ansible_collections/community/general/tests/integration/targets/nomad/aliases @@ -8,3 +8,4 @@ destructive skip/aix skip/centos6 skip/freebsd +disabled # TODO diff --git a/ansible_collections/community/general/tests/integration/targets/npm/tasks/test.yml b/ansible_collections/community/general/tests/integration/targets/npm/tasks/test.yml index c8e83f602..0ab8a98d2 100644 --- a/ansible_collections/community/general/tests/integration/targets/npm/tasks/test.yml +++ b/ansible_collections/community/general/tests/integration/targets/npm/tasks/test.yml @@ -12,10 +12,10 @@ # sample: node-v8.2.0-linux-x64.tar.xz node_path: '{{ remote_dir }}/{{ nodejs_path }}/bin' package: 'iconv-lite' + environment: + PATH: '{{ node_path }}:{{ ansible_env.PATH }}' block: - shell: npm --version - environment: - PATH: '{{ node_path }}:{{ ansible_env.PATH }}' register: npm_version - debug: @@ -24,11 +24,8 @@ - name: 'Install simple package without dependency' npm: path: '{{ remote_dir }}' - executable: '{{ node_path }}/npm' state: present name: '{{ package }}' - environment: - PATH: '{{ node_path }}:{{ ansible_env.PATH }}' register: npm_install - assert: @@ -39,11 +36,8 @@ - name: 'Reinstall simple package without dependency' npm: path: '{{ remote_dir }}' - executable: '{{ node_path }}/npm' state: present name: '{{ package }}' - environment: - PATH: '{{ node_path }}:{{ ansible_env.PATH }}' register: npm_reinstall - name: Check there is no change @@ -60,11 +54,8 @@ - name: 'reinstall simple package' npm: path: '{{ remote_dir }}' - executable: '{{ node_path }}/npm' state: present name: '{{ package }}' - environment: - PATH: '{{ node_path }}:{{ ansible_env.PATH }}' register: npm_fix_install - name: Check result is changed and successful diff --git a/ansible_collections/community/general/tests/integration/targets/odbc/aliases b/ansible_collections/community/general/tests/integration/targets/odbc/aliases index e8465c50e..91a616725 100644 --- a/ansible_collections/community/general/tests/integration/targets/odbc/aliases +++ b/ansible_collections/community/general/tests/integration/targets/odbc/aliases @@ -9,4 +9,6 @@ skip/macos skip/rhel8.0 skip/rhel9.0 skip/rhel9.1 +skip/rhel9.2 +skip/rhel9.3 skip/freebsd diff --git a/ansible_collections/community/general/tests/integration/targets/odbc/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/odbc/tasks/main.yml index ce55ea8aa..af5f57cb2 100644 --- a/ansible_collections/community/general/tests/integration/targets/odbc/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/odbc/tasks/main.yml @@ -10,6 +10,7 @@ - when: - ansible_os_family != 'Archlinux' # TODO install driver from AUR: https://aur.archlinux.org/packages/psqlodbc + - ansible_os_family != 'RedHat' or ansible_distribution_major_version != '7' # CentOS 7 stopped working block: # diff --git a/ansible_collections/community/general/tests/integration/targets/one_host/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/one_host/tasks/main.yml index ffd5ac04c..3b2c1cedf 100644 --- a/ansible_collections/community/general/tests/integration/targets/one_host/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/one_host/tasks/main.yml @@ -9,7 +9,7 @@ # 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 -# ENVIRONENT PREPARACTION +# ENVIRONMENT PREPARACTION - set_fact: test_number= 0 diff --git a/ansible_collections/community/general/tests/integration/targets/osx_defaults/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/osx_defaults/tasks/main.yml index f7bcb8944..3ca3180f0 100644 --- a/ansible_collections/community/general/tests/integration/targets/osx_defaults/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/osx_defaults/tasks/main.yml @@ -21,7 +21,7 @@ - name: Test if state and value are required together assert: that: - - "'following are missing: value' in '{{ missing_value['msg'] }}'" + - "'following are missing: value' in missing_value['msg']" - name: Change value of AppleMeasurementUnits to centimeter in check_mode osx_defaults: @@ -194,7 +194,7 @@ register: test_data_types - assert: - that: "{{ item.changed }}" + that: "item is changed" with_items: "{{ test_data_types.results }}" - name: Use different data types and delete them @@ -208,7 +208,7 @@ register: test_data_types - assert: - that: "{{ item.changed }}" + that: "item is changed" with_items: "{{ test_data_types.results }}" diff --git a/ansible_collections/community/general/tests/integration/targets/pacman/handlers/main.yml b/ansible_collections/community/general/tests/integration/targets/pacman/handlers/main.yml new file mode 100644 index 000000000..484482bba --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/pacman/handlers/main.yml @@ -0,0 +1,23 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Remove user yaybuilder + ansible.builtin.user: + name: yaybuilder + state: absent + +- name: Remove yay + ansible.builtin.package: + name: yay + state: absent + +- name: Remove packages for yay-become + ansible.builtin.package: + name: + - base-devel + - yay + - git + - nmap + state: absent diff --git a/ansible_collections/community/general/tests/integration/targets/pacman/tasks/locally_installed_package.yml b/ansible_collections/community/general/tests/integration/targets/pacman/tasks/locally_installed_package.yml index a5f183236..f67950c75 100644 --- a/ansible_collections/community/general/tests/integration/targets/pacman/tasks/locally_installed_package.yml +++ b/ansible_collections/community/general/tests/integration/targets/pacman/tasks/locally_installed_package.yml @@ -7,11 +7,12 @@ package_name: ansible-test-foo username: ansible-regular-user block: - - name: Install fakeroot + - name: Install dependencies pacman: state: present name: - fakeroot + - debugedit - name: Create user user: diff --git a/ansible_collections/community/general/tests/integration/targets/pacman/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/pacman/tasks/main.yml index 12d28a2d3..1f6001a66 100644 --- a/ansible_collections/community/general/tests/integration/targets/pacman/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/pacman/tasks/main.yml @@ -17,3 +17,4 @@ - include_tasks: 'update_cache.yml' - include_tasks: 'locally_installed_package.yml' - include_tasks: 'reason.yml' + - include_tasks: 'yay-become.yml' diff --git a/ansible_collections/community/general/tests/integration/targets/pacman/tasks/remove_nosave.yml b/ansible_collections/community/general/tests/integration/targets/pacman/tasks/remove_nosave.yml index 2271ebc03..a410775c2 100644 --- a/ansible_collections/community/general/tests/integration/targets/pacman/tasks/remove_nosave.yml +++ b/ansible_collections/community/general/tests/integration/targets/pacman/tasks/remove_nosave.yml @@ -4,13 +4,14 @@ # SPDX-License-Identifier: GPL-3.0-or-later - vars: - package_name: xinetd - config_file: /etc/xinetd.conf + package_name: mdadm + config_file: /etc/mdadm.conf block: - name: Make sure that {{ package_name }} is not installed pacman: name: '{{ package_name }}' state: absent + - name: Make sure {{config_file}}.pacsave file doesn't exist file: path: '{{config_file}}.pacsave' @@ -32,6 +33,7 @@ pacman: name: '{{ package_name }}' state: absent + - name: Make sure {{config_file}}.pacsave exists stat: path: '{{config_file}}.pacsave' diff --git a/ansible_collections/community/general/tests/integration/targets/pacman/tasks/yay-become.yml b/ansible_collections/community/general/tests/integration/targets/pacman/tasks/yay-become.yml new file mode 100644 index 000000000..95698d5ce --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/pacman/tasks/yay-become.yml @@ -0,0 +1,66 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# This is more convoluted that one might expect, because: +# - yay is not available or installation in ArchLinux (as it is in Manjaro - issue 6184 reports using it) +# - to install yay in ArchLinux requires building the package +# - makepkg cannot be run as root, but the user running it must have sudo to install the resulting package + +- name: create user + ansible.builtin.user: + name: yaybuilder + state: present + notify: Remove user yaybuilder + +- name: grant sudo powers to builder + community.general.sudoers: + name: yaybuilder + user: yaybuilder + commands: ALL + nopassword: true + +- name: Install base packages + ansible.builtin.package: + name: + - base-devel + - git + - go + state: present + notify: Remove packages for yay-become + +- name: Hack permissions for the remote_tmp_dir + ansible.builtin.file: + path: "{{ remote_tmp_dir }}" + mode: '0777' + +- name: Create temp directory for builder + ansible.builtin.file: + path: "{{ remote_tmp_dir }}/builder" + owner: yaybuilder + state: directory + mode: '0755' + +- name: clone yay git repo + become: true + become_user: yaybuilder + ansible.builtin.git: + repo: https://aur.archlinux.org/yay.git + dest: "{{ remote_tmp_dir }}/builder/yay" + depth: 1 + +- name: make package + become: true + become_user: yaybuilder + ansible.builtin.command: + chdir: "{{ remote_tmp_dir }}/builder/yay" + cmd: makepkg -si --noconfirm + notify: Remove yay + +- name: Install nmap + community.general.pacman: + name: nmap + state: present + executable: yay + extra_args: --builddir /var/cache/yay diff --git a/ansible_collections/community/general/tests/integration/targets/pids/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/pids/tasks/main.yml index 2ba7f3754..c8feaacf3 100644 --- a/ansible_collections/community/general/tests/integration/targets/pids/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/pids/tasks/main.yml @@ -45,7 +45,7 @@ - name: Copy templated helper script template: - src: obtainpid.sh + src: obtainpid.sh.j2 dest: "{{ remote_tmp_dir }}/obtainpid.sh" mode: 0755 diff --git a/ansible_collections/community/general/tests/integration/targets/pids/templates/obtainpid.sh b/ansible_collections/community/general/tests/integration/targets/pids/templates/obtainpid.sh.j2 index ecbf56aab..ecbf56aab 100644 --- a/ansible_collections/community/general/tests/integration/targets/pids/templates/obtainpid.sh +++ b/ansible_collections/community/general/tests/integration/targets/pids/templates/obtainpid.sh.j2 diff --git a/ansible_collections/community/general/tests/integration/targets/pipx/aliases b/ansible_collections/community/general/tests/integration/targets/pipx/aliases index 9f87ec348..66e6e1a3e 100644 --- a/ansible_collections/community/general/tests/integration/targets/pipx/aliases +++ b/ansible_collections/community/general/tests/integration/targets/pipx/aliases @@ -6,3 +6,4 @@ azp/posix/2 destructive skip/python2 skip/python3.5 +disabled # TODO diff --git a/ansible_collections/community/general/tests/integration/targets/pipx/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/pipx/tasks/main.yml index 567405ec4..7eb0f11a6 100644 --- a/ansible_collections/community/general/tests/integration/targets/pipx/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/pipx/tasks/main.yml @@ -314,3 +314,28 @@ that: - install_tox_sitewide is changed - usrlocaltox.stat.exists + +############################################################################## +# Test for issue 7497 +- name: ensure application pyinstaller is uninstalled + community.general.pipx: + name: pyinstaller + state: absent + +- name: Install Python Package pyinstaller + community.general.pipx: + name: pyinstaller + state: present + system_site_packages: true + pip_args: "--no-cache-dir" + register: install_pyinstaller + +- name: cleanup pyinstaller + community.general.pipx: + name: pyinstaller + state: absent + +- name: check assertions + assert: + that: + - install_pyinstaller is changed diff --git a/ansible_collections/community/general/tests/integration/targets/pipx_info/aliases b/ansible_collections/community/general/tests/integration/targets/pipx_info/aliases index a28278bbc..e262b485a 100644 --- a/ansible_collections/community/general/tests/integration/targets/pipx_info/aliases +++ b/ansible_collections/community/general/tests/integration/targets/pipx_info/aliases @@ -6,3 +6,4 @@ azp/posix/3 destructive skip/python2 skip/python3.5 +disabled # TODO diff --git a/ansible_collections/community/general/tests/integration/targets/pkgng/tasks/freebsd.yml b/ansible_collections/community/general/tests/integration/targets/pkgng/tasks/freebsd.yml index 0c8001899..9d4ecf8bb 100644 --- a/ansible_collections/community/general/tests/integration/targets/pkgng/tasks/freebsd.yml +++ b/ansible_collections/community/general/tests/integration/targets/pkgng/tasks/freebsd.yml @@ -515,11 +515,18 @@ # NOTE: FreeBSD 13.2 fails to update the package catalogue for unknown reasons (someone with FreeBSD # knowledge has to take a look) # + # NOTE: FreeBSD 13.3 fails to update the package catalogue for unknown reasons (someone with FreeBSD + # knowledge has to take a look) + # + # NOTE: FreeBSD 14.0 fails to update the package catalogue for unknown reasons (someone with FreeBSD + # knowledge has to take a look) + # # See also # https://github.com/ansible-collections/community.general/issues/5795 when: >- (ansible_distribution_version is version('12.01', '>=') and ansible_distribution_version is version('12.3', '<')) - or ansible_distribution_version is version('13.3', '>=') + or (ansible_distribution_version is version('13.4', '>=') and ansible_distribution_version is version('14.0', '<')) + or ansible_distribution_version is version('14.1', '>=') block: - name: Setup testjail include_tasks: setup-testjail.yml diff --git a/ansible_collections/community/general/tests/integration/targets/pnpm/aliases b/ansible_collections/community/general/tests/integration/targets/pnpm/aliases new file mode 100644 index 000000000..afeb1a6ee --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/pnpm/aliases @@ -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 diff --git a/ansible_collections/community/general/tests/integration/targets/pnpm/meta/main.yml b/ansible_collections/community/general/tests/integration/targets/pnpm/meta/main.yml new file mode 100644 index 000000000..6147ad33e --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/pnpm/meta/main.yml @@ -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 diff --git a/ansible_collections/community/general/tests/integration/targets/pnpm/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/pnpm/tasks/main.yml new file mode 100644 index 000000000..10f42618f --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/pnpm/tasks/main.yml @@ -0,0 +1,27 @@ +--- +#################################################################### +# 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 + ansible.builtin.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') diff --git a/ansible_collections/community/general/tests/integration/targets/pnpm/tasks/run.yml b/ansible_collections/community/general/tests/integration/targets/pnpm/tasks/run.yml new file mode 100644 index 000000000..66f5eb622 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/pnpm/tasks/run.yml @@ -0,0 +1,322 @@ +--- +# 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: 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 not 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: Updating package globally, without explicit executable path + pnpm: + name: "{{ package }}" + state: latest + 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 + - pnpm_reinstall is not 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 diff --git a/ansible_collections/community/general/tests/integration/targets/pnpm/templates/package.j2 b/ansible_collections/community/general/tests/integration/targets/pnpm/templates/package.j2 new file mode 100644 index 000000000..429ea9bee --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/pnpm/templates/package.j2 @@ -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" + } +} diff --git a/ansible_collections/community/general/tests/integration/targets/proxmox/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/proxmox/tasks/main.yml index 22d7fcd29..1b529d111 100644 --- a/ansible_collections/community/general/tests/integration/targets/proxmox/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/proxmox/tasks/main.yml @@ -129,6 +129,25 @@ - results_storage.proxmox_storages|length == 1 - results_storage.proxmox_storages[0].storage == "{{ storage }}" +- name: List content on storage + proxmox_storage_contents_info: + api_host: "{{ api_host }}" + api_user: "{{ user }}@{{ domain }}" + api_password: "{{ api_password | default(omit) }}" + api_token_id: "{{ api_token_id | default(omit) }}" + api_token_secret: "{{ api_token_secret | default(omit) }}" + validate_certs: "{{ validate_certs }}" + storage: "{{ storage }}" + node: "{{ node }}" + content: images + register: results_list_storage + +- assert: + that: + - results_storage is not changed + - results_storage.proxmox_storage_content is defined + - results_storage.proxmox_storage_content |length == 1 + - name: VM creation tags: [ 'create' ] block: @@ -577,3 +596,20 @@ - results_kvm_destroy is changed - results_kvm_destroy.vmid == {{ vmid }} - results_kvm_destroy.msg == "VM {{ vmid }} removed" + +- name: Retrieve information about nodes + proxmox_node_info: + api_host: "{{ api_host }}" + api_user: "{{ user }}@{{ domain }}" + api_password: "{{ api_password | default(omit) }}" + api_token_id: "{{ api_token_id | default(omit) }}" + api_token_secret: "{{ api_token_secret | default(omit) }}" + validate_certs: "{{ validate_certs }}" + register: results + +- assert: + that: + - results is not changed + - results.proxmox_nodes is defined + - results.proxmox_nodes|length >= 1 + - results.proxmox_nodes[0].type == 'node' diff --git a/ansible_collections/community/general/tests/integration/targets/proxmox_pool/aliases b/ansible_collections/community/general/tests/integration/targets/proxmox_pool/aliases new file mode 100644 index 000000000..525dcd332 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/proxmox_pool/aliases @@ -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 + +unsupported +proxmox_pool +proxmox_pool_member diff --git a/ansible_collections/community/general/tests/integration/targets/proxmox_pool/defaults/main.yml b/ansible_collections/community/general/tests/integration/targets/proxmox_pool/defaults/main.yml new file mode 100644 index 000000000..5a518ac73 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/proxmox_pool/defaults/main.yml @@ -0,0 +1,7 @@ +# Copyright (c) 2023, Sergei Antipov <greendayonfire at gmail.com> +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +poolid: test +member: local +member_type: storage diff --git a/ansible_collections/community/general/tests/integration/targets/proxmox_pool/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/proxmox_pool/tasks/main.yml new file mode 100644 index 000000000..2b22960f2 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/proxmox_pool/tasks/main.yml @@ -0,0 +1,220 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Copyright (c) 2023, Sergei Antipov <greendayonfire at gmail.com> +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Proxmox VE pool and pool membership management + tags: ["pool"] + block: + - name: Make sure poolid parameter is not missing + proxmox_pool: + api_host: "{{ api_host }}" + api_user: "{{ user }}@{{ domain }}" + api_password: "{{ api_password | default(omit) }}" + api_token_id: "{{ api_token_id | default(omit) }}" + api_token_secret: "{{ api_token_secret | default(omit) }}" + validate_certs: "{{ validate_certs }}" + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "'missing required arguments: poolid' in result.msg" + + - name: Create pool (Check) + proxmox_pool: + api_host: "{{ api_host }}" + api_user: "{{ user }}@{{ domain }}" + api_password: "{{ api_password | default(omit) }}" + api_token_id: "{{ api_token_id | default(omit) }}" + api_token_secret: "{{ api_token_secret | default(omit) }}" + validate_certs: "{{ validate_certs }}" + poolid: "{{ poolid }}" + check_mode: true + register: result + + - assert: + that: + - result is changed + - result is success + + - name: Create pool + proxmox_pool: + api_host: "{{ api_host }}" + api_user: "{{ user }}@{{ domain }}" + api_password: "{{ api_password | default(omit) }}" + api_token_id: "{{ api_token_id | default(omit) }}" + api_token_secret: "{{ api_token_secret | default(omit) }}" + validate_certs: "{{ validate_certs }}" + poolid: "{{ poolid }}" + register: result + + - assert: + that: + - result is changed + - result is success + - result.poolid == "{{ poolid }}" + + - name: Delete pool (Check) + proxmox_pool: + api_host: "{{ api_host }}" + api_user: "{{ user }}@{{ domain }}" + api_password: "{{ api_password | default(omit) }}" + api_token_id: "{{ api_token_id | default(omit) }}" + api_token_secret: "{{ api_token_secret | default(omit) }}" + validate_certs: "{{ validate_certs }}" + poolid: "{{ poolid }}" + state: absent + check_mode: true + register: result + + - assert: + that: + - result is changed + - result is success + + - name: Delete non-existing pool should do nothing + proxmox_pool: + api_host: "{{ api_host }}" + api_user: "{{ user }}@{{ domain }}" + api_password: "{{ api_password | default(omit) }}" + api_token_id: "{{ api_token_id | default(omit) }}" + api_token_secret: "{{ api_token_secret | default(omit) }}" + validate_certs: "{{ validate_certs }}" + poolid: "non-existing-poolid" + state: absent + register: result + + - assert: + that: + - result is not changed + - result is success + + - name: Deletion of non-empty pool fails + block: + - name: Add storage into pool + proxmox_pool_member: + api_host: "{{ api_host }}" + api_user: "{{ user }}@{{ domain }}" + api_password: "{{ api_password | default(omit) }}" + api_token_id: "{{ api_token_id | default(omit) }}" + api_token_secret: "{{ api_token_secret | default(omit) }}" + validate_certs: "{{ validate_certs }}" + poolid: "{{ poolid }}" + member: "{{ member }}" + type: "{{ member_type }}" + diff: true + register: result + + - assert: + that: + - result is changed + - result is success + - "'{{ member }}' in result.diff.after.members" + + - name: Add non-existing storage into pool should fail + proxmox_pool_member: + api_host: "{{ api_host }}" + api_user: "{{ user }}@{{ domain }}" + api_password: "{{ api_password | default(omit) }}" + api_token_id: "{{ api_token_id | default(omit) }}" + api_token_secret: "{{ api_token_secret | default(omit) }}" + validate_certs: "{{ validate_certs }}" + poolid: "{{ poolid }}" + member: "non-existing-storage" + type: "{{ member_type }}" + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "'Storage non-existing-storage doesn\\'t exist in the cluster' in result.msg" + + - name: Delete non-empty pool + proxmox_pool: + api_host: "{{ api_host }}" + api_user: "{{ user }}@{{ domain }}" + api_password: "{{ api_password | default(omit) }}" + api_token_id: "{{ api_token_id | default(omit) }}" + api_token_secret: "{{ api_token_secret | default(omit) }}" + validate_certs: "{{ validate_certs }}" + poolid: "{{ poolid }}" + state: absent + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "'Please remove members from pool first.' in result.msg" + + - name: Delete storage from the pool + proxmox_pool_member: + api_host: "{{ api_host }}" + api_user: "{{ user }}@{{ domain }}" + api_password: "{{ api_password | default(omit) }}" + api_token_id: "{{ api_token_id | default(omit) }}" + api_token_secret: "{{ api_token_secret | default(omit) }}" + validate_certs: "{{ validate_certs }}" + poolid: "{{ poolid }}" + member: "{{ member }}" + type: "{{ member_type }}" + state: absent + register: result + + - assert: + that: + - result is success + - result is changed + + rescue: + - name: Delete storage from the pool if it is added + proxmox_pool_member: + api_host: "{{ api_host }}" + api_user: "{{ user }}@{{ domain }}" + api_password: "{{ api_password | default(omit) }}" + api_token_id: "{{ api_token_id | default(omit) }}" + api_token_secret: "{{ api_token_secret | default(omit) }}" + validate_certs: "{{ validate_certs }}" + poolid: "{{ poolid }}" + member: "{{ member }}" + type: "{{ member_type }}" + state: absent + ignore_errors: true + + - name: Delete pool + proxmox_pool: + api_host: "{{ api_host }}" + api_user: "{{ user }}@{{ domain }}" + api_password: "{{ api_password | default(omit) }}" + api_token_id: "{{ api_token_id | default(omit) }}" + api_token_secret: "{{ api_token_secret | default(omit) }}" + validate_certs: "{{ validate_certs }}" + poolid: "{{ poolid }}" + state: absent + register: result + + - assert: + that: + - result is changed + - result is success + - result.poolid == "{{ poolid }}" + + rescue: + - name: Delete test pool if it is created + proxmox_pool: + api_host: "{{ api_host }}" + api_user: "{{ user }}@{{ domain }}" + api_password: "{{ api_password | default(omit) }}" + api_token_id: "{{ api_token_id | default(omit) }}" + api_token_secret: "{{ api_token_secret | default(omit) }}" + validate_certs: "{{ validate_certs }}" + poolid: "{{ poolid }}" + state: absent + ignore_errors: true diff --git a/ansible_collections/community/general/tests/integration/targets/proxmox_template/aliases b/ansible_collections/community/general/tests/integration/targets/proxmox_template/aliases new file mode 100644 index 000000000..5d9af8101 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/proxmox_template/aliases @@ -0,0 +1,6 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +unsupported +proxmox_template diff --git a/ansible_collections/community/general/tests/integration/targets/proxmox_template/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/proxmox_template/tasks/main.yml new file mode 100644 index 000000000..2d1187e89 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/proxmox_template/tasks/main.yml @@ -0,0 +1,136 @@ +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Copyright (c) 2023, Sergei Antipov <greendayonfire at gmail.com> +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Proxmox VE virtual machines templates management + tags: ['template'] + vars: + filename: /tmp/dummy.iso + block: + - name: Create dummy ISO file + ansible.builtin.command: + cmd: 'truncate -s 300M {{ filename }}' + + - name: Delete requests_toolbelt module if it is installed + ansible.builtin.pip: + name: requests_toolbelt + state: absent + + - name: Install latest proxmoxer + ansible.builtin.pip: + name: proxmoxer + state: latest + + - name: Upload ISO as template to Proxmox VE cluster should fail + proxmox_template: + api_host: '{{ api_host }}' + api_user: '{{ user }}@{{ domain }}' + api_password: '{{ api_password | default(omit) }}' + api_token_id: '{{ api_token_id | default(omit) }}' + api_token_secret: '{{ api_token_secret | default(omit) }}' + validate_certs: '{{ validate_certs }}' + node: '{{ node }}' + src: '{{ filename }}' + content_type: iso + force: true + register: result + ignore_errors: true + + - assert: + that: + - result is failed + - result.msg is match('\'requests_toolbelt\' module is required to upload files larger than 256MB') + + - name: Install old (1.1.2) version of proxmoxer + ansible.builtin.pip: + name: proxmoxer==1.1.1 + state: present + + - name: Upload ISO as template to Proxmox VE cluster should be successful + proxmox_template: + api_host: '{{ api_host }}' + api_user: '{{ user }}@{{ domain }}' + api_password: '{{ api_password | default(omit) }}' + api_token_id: '{{ api_token_id | default(omit) }}' + api_token_secret: '{{ api_token_secret | default(omit) }}' + validate_certs: '{{ validate_certs }}' + node: '{{ node }}' + src: '{{ filename }}' + content_type: iso + force: true + register: result + + - assert: + that: + - result is changed + - result is success + - result.msg is match('template with volid=local:iso/dummy.iso uploaded') + + - name: Install latest proxmoxer + ansible.builtin.pip: + name: proxmoxer + state: latest + + - name: Make smaller dummy file + ansible.builtin.command: + cmd: 'truncate -s 128M {{ filename }}' + + - name: Upload ISO as template to Proxmox VE cluster should be successful + proxmox_template: + api_host: '{{ api_host }}' + api_user: '{{ user }}@{{ domain }}' + api_password: '{{ api_password | default(omit) }}' + api_token_id: '{{ api_token_id | default(omit) }}' + api_token_secret: '{{ api_token_secret | default(omit) }}' + validate_certs: '{{ validate_certs }}' + node: '{{ node }}' + src: '{{ filename }}' + content_type: iso + force: true + register: result + + - assert: + that: + - result is changed + - result is success + - result.msg is match('template with volid=local:iso/dummy.iso uploaded') + + - name: Install requests_toolbelt + ansible.builtin.pip: + name: requests_toolbelt + state: present + + - name: Make big dummy file + ansible.builtin.command: + cmd: 'truncate -s 300M {{ filename }}' + + - name: Upload ISO as template to Proxmox VE cluster should be successful + proxmox_template: + api_host: '{{ api_host }}' + api_user: '{{ user }}@{{ domain }}' + api_password: '{{ api_password | default(omit) }}' + api_token_id: '{{ api_token_id | default(omit) }}' + api_token_secret: '{{ api_token_secret | default(omit) }}' + validate_certs: '{{ validate_certs }}' + node: '{{ node }}' + src: '{{ filename }}' + content_type: iso + force: true + register: result + + - assert: + that: + - result is changed + - result is success + - result.msg is match('template with volid=local:iso/dummy.iso uploaded') + + always: + - name: Delete ISO file from host + ansible.builtin.file: + path: '{{ filename }}' + state: absent diff --git a/ansible_collections/community/general/tests/integration/targets/scaleway_compute/tasks/pagination.yml b/ansible_collections/community/general/tests/integration/targets/scaleway_compute/tasks/pagination.yml index 5a674b801..b2429e39f 100644 --- a/ansible_collections/community/general/tests/integration/targets/scaleway_compute/tasks/pagination.yml +++ b/ansible_collections/community/general/tests/integration/targets/scaleway_compute/tasks/pagination.yml @@ -23,7 +23,7 @@ commercial_type: '{{ scaleway_commerial_type }}' wait: true -- name: Get server informations of the first page +- name: Get server information of the first page scaleway_server_info: region: par1 query_parameters: @@ -37,7 +37,7 @@ that: - first_page is success -- name: Get server informations of the second page +- name: Get server information of the second page scaleway_server_info: region: par1 query_parameters: diff --git a/ansible_collections/community/general/tests/integration/targets/scaleway_container_registry/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/scaleway_container_registry/tasks/main.yml index 91cea20f3..84d733a10 100644 --- a/ansible_collections/community/general/tests/integration/targets/scaleway_container_registry/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/scaleway_container_registry/tasks/main.yml @@ -159,7 +159,7 @@ - cr_deletion_task is success - cr_deletion_task is changed -- name: Delete container regitry (Confirmation) +- name: Delete container registry (Confirmation) community.general.scaleway_container_registry: state: absent name: '{{ name }}' diff --git a/ansible_collections/community/general/tests/integration/targets/scaleway_image_info/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/scaleway_image_info/tasks/main.yml index 2cdf34fdd..3dfe20f37 100644 --- a/ansible_collections/community/general/tests/integration/targets/scaleway_image_info/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/scaleway_image_info/tasks/main.yml @@ -8,7 +8,7 @@ # 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: Get image informations and register it in a variable +- name: Get image information and register it in a variable scaleway_image_info: region: par1 register: images @@ -22,7 +22,7 @@ that: - images is success -- name: Get image informations from ams1 and register it in a variable +- name: Get image information from ams1 and register it in a variable scaleway_image_info: region: ams1 register: images_ams1 diff --git a/ansible_collections/community/general/tests/integration/targets/scaleway_ip_info/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/scaleway_ip_info/tasks/main.yml index b560b5658..6aad9b52e 100644 --- a/ansible_collections/community/general/tests/integration/targets/scaleway_ip_info/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/scaleway_ip_info/tasks/main.yml @@ -8,7 +8,7 @@ # 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: Get ip informations and register it in a variable +- name: Get ip information and register it in a variable scaleway_ip_info: region: par1 register: ips @@ -22,7 +22,7 @@ that: - ips is success -- name: Get ip informations and register it in a variable +- name: Get ip information and register it in a variable scaleway_ip_info: region: ams1 register: ips_ams1 diff --git a/ansible_collections/community/general/tests/integration/targets/scaleway_organization_info/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/scaleway_organization_info/tasks/main.yml index 7326ca226..247a4012b 100644 --- a/ansible_collections/community/general/tests/integration/targets/scaleway_organization_info/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/scaleway_organization_info/tasks/main.yml @@ -8,7 +8,7 @@ # 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: Get organization informations and register it in a variable +- name: Get organization information and register it in a variable scaleway_organization_info: register: organizations diff --git a/ansible_collections/community/general/tests/integration/targets/scaleway_security_group_info/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/scaleway_security_group_info/tasks/main.yml index 8029a1e9a..608d76409 100644 --- a/ansible_collections/community/general/tests/integration/targets/scaleway_security_group_info/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/scaleway_security_group_info/tasks/main.yml @@ -8,7 +8,7 @@ # 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: Get security group informations and register it in a variable +- name: Get security group information and register it in a variable scaleway_security_group_info: region: par1 register: security_groups @@ -22,7 +22,7 @@ that: - security_groups is success -- name: Get security group informations and register it in a variable (AMS1) +- name: Get security group information and register it in a variable (AMS1) scaleway_security_group_info: region: ams1 register: ams1_security_groups diff --git a/ansible_collections/community/general/tests/integration/targets/scaleway_server_info/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/scaleway_server_info/tasks/main.yml index 7274e8a85..516b2df0e 100644 --- a/ansible_collections/community/general/tests/integration/targets/scaleway_server_info/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/scaleway_server_info/tasks/main.yml @@ -8,7 +8,7 @@ # 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: Get server informations and register it in a variable +- name: Get server information and register it in a variable scaleway_server_info: region: par1 register: servers @@ -22,7 +22,7 @@ that: - servers is success -- name: Get server informations and register it in a variable +- name: Get server information and register it in a variable scaleway_server_info: region: ams1 register: ams1_servers diff --git a/ansible_collections/community/general/tests/integration/targets/scaleway_snapshot_info/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/scaleway_snapshot_info/tasks/main.yml index 44f15d515..f80a9ced1 100644 --- a/ansible_collections/community/general/tests/integration/targets/scaleway_snapshot_info/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/scaleway_snapshot_info/tasks/main.yml @@ -8,7 +8,7 @@ # 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: Get snapshot informations and register it in a variable +- name: Get snapshot information and register it in a variable scaleway_snapshot_info: region: par1 register: snapshots @@ -22,7 +22,7 @@ that: - snapshots is success -- name: Get snapshot informations and register it in a variable (AMS1) +- name: Get snapshot information and register it in a variable (AMS1) scaleway_snapshot_info: region: ams1 register: ams1_snapshots diff --git a/ansible_collections/community/general/tests/integration/targets/scaleway_volume_info/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/scaleway_volume_info/tasks/main.yml index 45995a54c..2202e6bf9 100644 --- a/ansible_collections/community/general/tests/integration/targets/scaleway_volume_info/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/scaleway_volume_info/tasks/main.yml @@ -8,7 +8,7 @@ # 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: Get volume informations and register it in a variable +- name: Get volume information and register it in a variable scaleway_volume_info: region: par1 register: volumes @@ -22,7 +22,7 @@ that: - volumes is success -- name: Get volume informations and register it in a variable (AMS1) +- name: Get volume information and register it in a variable (AMS1) scaleway_volume_info: region: ams1 register: ams1_volumes diff --git a/ansible_collections/community/general/tests/integration/targets/setup_docker/handlers/main.yml b/ansible_collections/community/general/tests/integration/targets/setup_docker/handlers/main.yml index 283496714..52e13e3c4 100644 --- a/ansible_collections/community/general/tests/integration/targets/setup_docker/handlers/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/setup_docker/handlers/main.yml @@ -3,17 +3,29 @@ # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # SPDX-License-Identifier: GPL-3.0-or-later +- name: Remove python requests + ansible.builtin.pip: + name: + - requests + state: absent + +- name: Stop docker service + become: true + ansible.builtin.service: + name: docker + state: stopped + - name: Remove Docker packages - package: + ansible.builtin.package: name: "{{ docker_packages }}" state: absent - name: "D-Fedora : Remove repository" - file: + ansible.builtin.file: path: /etc/yum.repos.d/docker-ce.repo state: absent - name: "D-Fedora : Remove dnf-plugins-core" - package: + ansible.builtin.package: name: dnf-plugins-core state: absent diff --git a/ansible_collections/community/general/tests/integration/targets/setup_docker/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/setup_docker/tasks/main.yml index 4f41da31a..19bc7aa8c 100644 --- a/ansible_collections/community/general/tests/integration/targets/setup_docker/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/setup_docker/tasks/main.yml @@ -41,6 +41,7 @@ ansible.builtin.service: name: docker state: started + notify: Stop docker service - name: Cheat on the docker socket permissions become: true @@ -53,3 +54,4 @@ name: - requests state: present + notify: Remove python requests diff --git a/ansible_collections/community/general/tests/integration/targets/setup_etcd3/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/setup_etcd3/tasks/main.yml index fe6b9cd02..1da52e225 100644 --- a/ansible_collections/community/general/tests/integration/targets/setup_etcd3/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/setup_etcd3/tasks/main.yml @@ -50,7 +50,7 @@ state: present # Check if re-installing etcd3 is required - - name: Check if etcd3ctl exists for re-use. + - name: Check if etcd3ctl exists for reuse. shell: "ETCDCTL_API=3 {{ etcd3_path }}/etcdctl --endpoints=localhost:2379 get foo" args: executable: /bin/bash diff --git a/ansible_collections/community/general/tests/integration/targets/setup_java_keytool/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/setup_java_keytool/tasks/main.yml index 2ab57d59d..9f156425d 100644 --- a/ansible_collections/community/general/tests/integration/targets/setup_java_keytool/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/setup_java_keytool/tasks/main.yml @@ -16,11 +16,19 @@ }} - name: Include OS-specific variables - include_vars: '{{ ansible_os_family }}.yml' + include_vars: '{{ lookup("first_found", params) }}' + vars: + params: + files: + - '{{ ansible_distribution }}-{{ ansible_distribution_version }}.yml' + - '{{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml' + - '{{ ansible_os_family }}.yml' + paths: + - '{{ role_path }}/vars' when: has_java_keytool - name: Install keytool package: - name: '{{ keytool_package_name }}' + name: '{{ keytool_package_names }}' become: true when: has_java_keytool diff --git a/ansible_collections/community/general/tests/integration/targets/setup_java_keytool/vars/Alpine.yml b/ansible_collections/community/general/tests/integration/targets/setup_java_keytool/vars/Alpine.yml index 4ff75ae8c..c314c80ba 100644 --- a/ansible_collections/community/general/tests/integration/targets/setup_java_keytool/vars/Alpine.yml +++ b/ansible_collections/community/general/tests/integration/targets/setup_java_keytool/vars/Alpine.yml @@ -3,4 +3,5 @@ # 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 -keytool_package_name: openjdk11-jre-headless +keytool_package_names: + - openjdk11-jre-headless diff --git a/ansible_collections/community/general/tests/integration/targets/setup_java_keytool/vars/Archlinux.yml b/ansible_collections/community/general/tests/integration/targets/setup_java_keytool/vars/Archlinux.yml index 9e29065b3..b342911b6 100644 --- a/ansible_collections/community/general/tests/integration/targets/setup_java_keytool/vars/Archlinux.yml +++ b/ansible_collections/community/general/tests/integration/targets/setup_java_keytool/vars/Archlinux.yml @@ -3,4 +3,5 @@ # 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 -keytool_package_name: jre11-openjdk-headless +keytool_package_names: + - jre11-openjdk-headless diff --git a/ansible_collections/community/general/tests/integration/targets/setup_java_keytool/vars/Debian-12.yml b/ansible_collections/community/general/tests/integration/targets/setup_java_keytool/vars/Debian-12.yml new file mode 100644 index 000000000..17f8a53d0 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/setup_java_keytool/vars/Debian-12.yml @@ -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 + +keytool_package_names: + - ca-certificates-java + - openjdk-17-jre-headless diff --git a/ansible_collections/community/general/tests/integration/targets/setup_java_keytool/vars/Debian.yml b/ansible_collections/community/general/tests/integration/targets/setup_java_keytool/vars/Debian.yml index 30ae5cd04..cb5551a58 100644 --- a/ansible_collections/community/general/tests/integration/targets/setup_java_keytool/vars/Debian.yml +++ b/ansible_collections/community/general/tests/integration/targets/setup_java_keytool/vars/Debian.yml @@ -3,4 +3,5 @@ # 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 -keytool_package_name: ca-certificates-java +keytool_package_names: + - ca-certificates-java diff --git a/ansible_collections/community/general/tests/integration/targets/setup_java_keytool/vars/RedHat.yml b/ansible_collections/community/general/tests/integration/targets/setup_java_keytool/vars/RedHat.yml index c200091f8..8f4126e5d 100644 --- a/ansible_collections/community/general/tests/integration/targets/setup_java_keytool/vars/RedHat.yml +++ b/ansible_collections/community/general/tests/integration/targets/setup_java_keytool/vars/RedHat.yml @@ -3,4 +3,5 @@ # 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 -keytool_package_name: java-11-openjdk-headless +keytool_package_names: + - java-11-openjdk-headless diff --git a/ansible_collections/community/general/tests/integration/targets/setup_java_keytool/vars/Suse.yml b/ansible_collections/community/general/tests/integration/targets/setup_java_keytool/vars/Suse.yml index c200091f8..8f4126e5d 100644 --- a/ansible_collections/community/general/tests/integration/targets/setup_java_keytool/vars/Suse.yml +++ b/ansible_collections/community/general/tests/integration/targets/setup_java_keytool/vars/Suse.yml @@ -3,4 +3,5 @@ # 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 -keytool_package_name: java-11-openjdk-headless +keytool_package_names: + - java-11-openjdk-headless diff --git a/ansible_collections/community/general/tests/integration/targets/setup_openldap/files/cert_cnconfig.ldif b/ansible_collections/community/general/tests/integration/targets/setup_openldap/files/cert_cnconfig.ldif new file mode 100644 index 000000000..fc97e5f5c --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/setup_openldap/files/cert_cnconfig.ldif @@ -0,0 +1,15 @@ +dn: cn=config +add: olcTLSCACertificateFile +olcTLSCACertificateFile: /usr/local/share/ca-certificates/ca.crt +- +add: olcTLSCertificateFile +olcTLSCertificateFile: /etc/ldap/localhost.crt +- +add: olcTLSCertificateKeyFile +olcTLSCertificateKeyFile: /etc/ldap/localhost.key +- +add: olcAuthzRegexp +olcAuthzRegexp: {0}"UID=([^,]*)" uid=$1,ou=users,dc=example,dc=com +- +add: olcTLSVerifyClient +olcTLSVerifyClient: allow diff --git a/ansible_collections/community/general/tests/sanity/ignore-2.11.txt.license b/ansible_collections/community/general/tests/integration/targets/setup_openldap/files/cert_cnconfig.ldif.license index edff8c768..edff8c768 100644 --- a/ansible_collections/community/general/tests/sanity/ignore-2.11.txt.license +++ b/ansible_collections/community/general/tests/integration/targets/setup_openldap/files/cert_cnconfig.ldif.license diff --git a/ansible_collections/community/general/tests/integration/targets/setup_openldap/files/initial_config.ldif b/ansible_collections/community/general/tests/integration/targets/setup_openldap/files/initial_config.ldif index 8f8c537bd..cb21f2cfd 100644 --- a/ansible_collections/community/general/tests/integration/targets/setup_openldap/files/initial_config.ldif +++ b/ansible_collections/community/general/tests/integration/targets/setup_openldap/files/initial_config.ldif @@ -18,5 +18,24 @@ homeDirectory: /home/ldaptest cn: LDAP Test gecos: LDAP Test displayName: LDAP Test +userPassword: test1pass! mail: ldap.test@example.com sn: Test + +dn: uid=second,ou=users,dc=example,dc=com +uid: second +uidNumber: 1112 +gidNUmber: 102 +objectClass: top +objectClass: posixAccount +objectClass: shadowAccount +objectClass: person +objectClass: organizationalPerson +objectClass: inetOrgPerson +loginShell: /bin/sh +homeDirectory: /home/second +cn: Second Test +gecos: Second Test +displayName: Second Test +mail: second.test@example.com +sn: Test diff --git a/ansible_collections/community/general/tests/integration/targets/setup_openldap/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/setup_openldap/tasks/main.yml index 25077de16..00f8f6a10 100644 --- a/ansible_collections/community/general/tests/integration/targets/setup_openldap/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/setup_openldap/tasks/main.yml @@ -44,6 +44,22 @@ cmd: "export DEBIAN_FRONTEND=noninteractive; cat /root/debconf-slapd.conf | debconf-set-selections; dpkg-reconfigure -f noninteractive slapd" creates: "/root/slapd_configured" + - name: Enable secure ldap + lineinfile: + path: /etc/default/slapd + regexp: "^SLAPD_SERVICES" + line: 'SLAPD_SERVICES="ldap:/// ldaps:/// ldapi:///"' + + - name: Create certificates + shell: | + openssl req -x509 -batch -sha256 -days 1825 -newkey rsa:2048 -nodes -keyout /root/ca.key -out /usr/local/share/ca-certificates/ca.crt + openssl req -batch -sha256 -days 365 -newkey rsa:2048 -subj "/CN=$(hostname)" -addext "subjectAltName = DNS:localhost" -nodes -keyout /etc/ldap/localhost.key -out /etc/ldap/localhost.csr + openssl x509 -req -CA /usr/local/share/ca-certificates/ca.crt -CAkey /root/ca.key -CAcreateserial -in /etc/ldap/localhost.csr -out /etc/ldap/localhost.crt + chgrp openldap /etc/ldap/localhost.key + chmod 0640 /etc/ldap/localhost.key + openssl req -batch -sha256 -days 365 -newkey rsa:2048 -subj "/UID=ldaptest" -nodes -keyout /root/user.key -out /root/user.csr + openssl x509 -req -CA /usr/local/share/ca-certificates/ca.crt -CAkey /root/ca.key -CAcreateserial -in /root/user.csr -out /root/user.crt + - name: Start OpenLDAP service become: true service: @@ -61,10 +77,14 @@ mode: '0644' loop: - rootpw_cnconfig.ldif + - cert_cnconfig.ldif - initial_config.ldif - name: Configure admin password for cn=config - shell: "ldapmodify -Y EXTERNAL -H ldapi:/// -f /tmp/rootpw_cnconfig.ldif" + shell: "ldapmodify -Y EXTERNAL -H ldapi:/// -f /tmp/{{ item }}" + loop: + - rootpw_cnconfig.ldif + - cert_cnconfig.ldif - name: Add initial config become: true diff --git a/ansible_collections/community/general/tests/integration/targets/setup_pkg_mgr/tasks/archlinux.yml b/ansible_collections/community/general/tests/integration/targets/setup_pkg_mgr/tasks/archlinux.yml index fc75f84df..f471cc19a 100644 --- a/ansible_collections/community/general/tests/integration/targets/setup_pkg_mgr/tasks/archlinux.yml +++ b/ansible_collections/community/general/tests/integration/targets/setup_pkg_mgr/tasks/archlinux.yml @@ -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 diff --git a/ansible_collections/community/general/tests/integration/targets/setup_postgresql_db/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/setup_postgresql_db/tasks/main.yml index 3dac4a098..99668ebc9 100644 --- a/ansible_collections/community/general/tests/integration/targets/setup_postgresql_db/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/setup_postgresql_db/tasks/main.yml @@ -127,6 +127,32 @@ seconds: 5 when: ansible_os_family == 'Suse' +- name: Make installable on Arch + community.general.ini_file: + path: /usr/lib/systemd/system/postgresql.service + section: Service + option: "{{ item }}" + state: absent + loop: + - PrivateTmp + - ProtectHome + - ProtectSystem + - NoNewPrivileges + - ProtectControlGroups + - ProtectKernelModules + - ProtectKernelTunables + - PrivateDevices + - RestrictAddressFamilies + - RestrictNamespaces + - RestrictRealtime + - SystemCallArchitectures + when: ansible_distribution == 'Archlinux' + +- name: Make installable on Arch + systemd: + daemon_reload: true + when: ansible_distribution == 'Archlinux' + - name: Initialize postgres (Suse) service: name=postgresql state=started when: ansible_os_family == 'Suse' diff --git a/ansible_collections/community/general/tests/integration/targets/setup_postgresql_db/vars/Debian-12-py3.yml b/ansible_collections/community/general/tests/integration/targets/setup_postgresql_db/vars/Debian-12-py3.yml new file mode 100644 index 000000000..c92240237 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/setup_postgresql_db/vars/Debian-12-py3.yml @@ -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 + +postgresql_packages: + - "postgresql" + - "postgresql-common" + - "python3-psycopg2" + +pg_hba_location: "/etc/postgresql/15/main/pg_hba.conf" +pg_dir: "/var/lib/postgresql/15/main" +pg_ver: 15 diff --git a/ansible_collections/community/general/tests/integration/targets/setup_snap/tasks/D-RedHat-9.2.yml b/ansible_collections/community/general/tests/integration/targets/setup_snap/tasks/D-RedHat-9.2.yml new file mode 100644 index 000000000..5bbfaff12 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/setup_snap/tasks/D-RedHat-9.2.yml @@ -0,0 +1,6 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# Do nothing diff --git a/ansible_collections/community/general/tests/integration/targets/setup_snap/tasks/D-RedHat-9.3.yml b/ansible_collections/community/general/tests/integration/targets/setup_snap/tasks/D-RedHat-9.3.yml new file mode 100644 index 000000000..5bbfaff12 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/setup_snap/tasks/D-RedHat-9.3.yml @@ -0,0 +1,6 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +# Do nothing diff --git a/ansible_collections/community/general/tests/integration/targets/setup_tls/files/ca_certificate.pem b/ansible_collections/community/general/tests/integration/targets/setup_tls/files/ca_certificate.pem index 130e0a2da..a438d9266 100644 --- a/ansible_collections/community/general/tests/integration/targets/setup_tls/files/ca_certificate.pem +++ b/ansible_collections/community/general/tests/integration/targets/setup_tls/files/ca_certificate.pem @@ -1,7 +1,3 @@ -# 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 - -----BEGIN CERTIFICATE----- MIIDAjCCAeqgAwIBAgIJANguFROhaWocMA0GCSqGSIb3DQEBCwUAMDExIDAeBgNV BAMMF1RMU0dlblNlbGZTaWduZWR0Um9vdENBMQ0wCwYDVQQHDAQkJCQkMB4XDTE5 diff --git a/ansible_collections/community/general/tests/integration/targets/setup_tls/files/ca_certificate.pem.license b/ansible_collections/community/general/tests/integration/targets/setup_tls/files/ca_certificate.pem.license new file mode 100644 index 000000000..a1390a69e --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/setup_tls/files/ca_certificate.pem.license @@ -0,0 +1,3 @@ +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 diff --git a/ansible_collections/community/general/tests/integration/targets/setup_tls/files/ca_key.pem b/ansible_collections/community/general/tests/integration/targets/setup_tls/files/ca_key.pem index d9dc5ca0f..0a950eda0 100644 --- a/ansible_collections/community/general/tests/integration/targets/setup_tls/files/ca_key.pem +++ b/ansible_collections/community/general/tests/integration/targets/setup_tls/files/ca_key.pem @@ -1,7 +1,3 @@ -# 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 - -----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDqVt84czSxWnWW 4Ng6hmKE3NarbLsycwtjrYBokV7Kk7Mp7PrBbYF05FOgSdJLvL6grlRSQK2VPsXd diff --git a/ansible_collections/community/general/tests/integration/targets/setup_tls/files/ca_key.pem.license b/ansible_collections/community/general/tests/integration/targets/setup_tls/files/ca_key.pem.license new file mode 100644 index 000000000..a1390a69e --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/setup_tls/files/ca_key.pem.license @@ -0,0 +1,3 @@ +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 diff --git a/ansible_collections/community/general/tests/integration/targets/setup_tls/files/client_certificate.pem b/ansible_collections/community/general/tests/integration/targets/setup_tls/files/client_certificate.pem index 9e956e6b0..501d83897 100644 --- a/ansible_collections/community/general/tests/integration/targets/setup_tls/files/client_certificate.pem +++ b/ansible_collections/community/general/tests/integration/targets/setup_tls/files/client_certificate.pem @@ -1,7 +1,3 @@ -# 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 - -----BEGIN CERTIFICATE----- MIIDRjCCAi6gAwIBAgIBAjANBgkqhkiG9w0BAQsFADAxMSAwHgYDVQQDDBdUTFNH ZW5TZWxmU2lnbmVkdFJvb3RDQTENMAsGA1UEBwwEJCQkJDAeFw0xOTAxMTEwODMz diff --git a/ansible_collections/community/general/tests/integration/targets/setup_tls/files/client_certificate.pem.license b/ansible_collections/community/general/tests/integration/targets/setup_tls/files/client_certificate.pem.license new file mode 100644 index 000000000..a1390a69e --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/setup_tls/files/client_certificate.pem.license @@ -0,0 +1,3 @@ +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 diff --git a/ansible_collections/community/general/tests/integration/targets/setup_tls/files/client_key.pem b/ansible_collections/community/general/tests/integration/targets/setup_tls/files/client_key.pem index 3848ad7cf..850260a87 100644 --- a/ansible_collections/community/general/tests/integration/targets/setup_tls/files/client_key.pem +++ b/ansible_collections/community/general/tests/integration/targets/setup_tls/files/client_key.pem @@ -1,7 +1,3 @@ -# 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 - -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAqDPjkNxwpwlAAM/Shhk8FgfYUG1HwGV5v7LZW9v7jgKd6zcM QJQrP4IspgRxOiLupqytNOlZ/mfYm6iKw9i7gjsXLtucvIKKhutk4HT+bGvcEfuf diff --git a/ansible_collections/community/general/tests/integration/targets/setup_tls/files/client_key.pem.license b/ansible_collections/community/general/tests/integration/targets/setup_tls/files/client_key.pem.license new file mode 100644 index 000000000..a1390a69e --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/setup_tls/files/client_key.pem.license @@ -0,0 +1,3 @@ +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 diff --git a/ansible_collections/community/general/tests/integration/targets/setup_tls/files/server_certificate.pem b/ansible_collections/community/general/tests/integration/targets/setup_tls/files/server_certificate.pem index b714ddbfb..4a0ebc6ec 100644 --- a/ansible_collections/community/general/tests/integration/targets/setup_tls/files/server_certificate.pem +++ b/ansible_collections/community/general/tests/integration/targets/setup_tls/files/server_certificate.pem @@ -1,7 +1,3 @@ -# 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 - -----BEGIN CERTIFICATE----- MIIDRjCCAi6gAwIBAgIBATANBgkqhkiG9w0BAQsFADAxMSAwHgYDVQQDDBdUTFNH ZW5TZWxmU2lnbmVkdFJvb3RDQTENMAsGA1UEBwwEJCQkJDAeFw0xOTAxMTEwODMz diff --git a/ansible_collections/community/general/tests/integration/targets/setup_tls/files/server_certificate.pem.license b/ansible_collections/community/general/tests/integration/targets/setup_tls/files/server_certificate.pem.license new file mode 100644 index 000000000..a1390a69e --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/setup_tls/files/server_certificate.pem.license @@ -0,0 +1,3 @@ +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 diff --git a/ansible_collections/community/general/tests/integration/targets/setup_tls/files/server_key.pem b/ansible_collections/community/general/tests/integration/targets/setup_tls/files/server_key.pem index ec0134993..c79ab6480 100644 --- a/ansible_collections/community/general/tests/integration/targets/setup_tls/files/server_key.pem +++ b/ansible_collections/community/general/tests/integration/targets/setup_tls/files/server_key.pem @@ -1,7 +1,3 @@ -# 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 - -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAyMBKx8AHrEQX3fR4mZJgd1WIdvHNUJBPSPJ2MhySl9mQVIQM yvofNAZHEySfeNuualsgAh/8JeeF3v6HxVBaxmuL89Ks+FJC/yiNDhsNvGOKpyna diff --git a/ansible_collections/community/general/tests/integration/targets/setup_tls/files/server_key.pem.license b/ansible_collections/community/general/tests/integration/targets/setup_tls/files/server_key.pem.license new file mode 100644 index 000000000..a1390a69e --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/setup_tls/files/server_key.pem.license @@ -0,0 +1,3 @@ +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 diff --git a/ansible_collections/community/general/tests/integration/targets/shutdown/aliases b/ansible_collections/community/general/tests/integration/targets/shutdown/aliases index afda346c4..428e8289d 100644 --- a/ansible_collections/community/general/tests/integration/targets/shutdown/aliases +++ b/ansible_collections/community/general/tests/integration/targets/shutdown/aliases @@ -3,3 +3,4 @@ # SPDX-License-Identifier: GPL-3.0-or-later azp/posix/1 +destructive diff --git a/ansible_collections/community/general/tests/integration/targets/shutdown/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/shutdown/tasks/main.yml index dadeb6269..2c9bc6bd6 100644 --- a/ansible_collections/community/general/tests/integration/targets/shutdown/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/shutdown/tasks/main.yml @@ -7,13 +7,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 - -- name: Install systemd-sysv on Ubuntu 18 and Debian - apt: - name: systemd-sysv - state: present - when: (ansible_distribution == 'Ubuntu' and ansible_distribution_major_version is version('18', '>=')) or (ansible_distribution == 'Debian') - register: systemd_sysv_install +# - name: Execute shutdown with custom message and delay community.general.shutdown: @@ -34,29 +28,44 @@ - '"Custom Message" in shutdown_result["shutdown_command"]' - '"Shut down initiated by Ansible" in shutdown_result_minus["shutdown_command"]' - '"Custom Message" not in shutdown_result_minus["shutdown_command"]' - when: ansible_os_family not in ['Alpine', 'AIX'] + when: + - 'ansible_os_family not in ["Alpine", "AIX"]' + - '"systemctl" not in shutdown_result["shutdown_command"]' + - '"systemctl" not in shutdown_result_minus["shutdown_command"]' -- name: Verify shutdown command is present except Alpine, VMKernel +- name: Verify shutdown command is present except Alpine or AIX or systemd assert: that: '"shutdown" in shutdown_result["shutdown_command"]' - when: ansible_os_family != 'Alpine' and ansible_system != 'VMKernel' + when: + - "ansible_os_family != 'Alpine'" + - "ansible_system != 'VMKernel'" + - '"systemctl" not in shutdown_result["shutdown_command"]' -- name: Verify shutdown command is present in Alpine +- name: Verify shutdown command is present in Alpine except systemd assert: that: '"poweroff" in shutdown_result["shutdown_command"]' - when: ansible_os_family == 'Alpine' + when: + - "ansible_os_family == 'Alpine'" + - '"systemctl" not in shutdown_result["shutdown_command"]' -- name: Verify shutdown command is present in VMKernel + +- name: Verify shutdown command is present in VMKernel except systemd assert: that: '"halt" in shutdown_result["shutdown_command"]' - when: ansible_system == 'VMKernel' + when: + - "ansible_system == 'VMKernel'" + - '"systemctl" not in shutdown_result["shutdown_command"]' -- name: Verify shutdown delay is present in minutes in Linux +- name: Verify shutdown delay is present in minutes in Linux except systemd assert: that: - '"-h 1" in shutdown_result["shutdown_command"]' - '"-h 0" in shutdown_result_minus["shutdown_command"]' - when: ansible_system == 'Linux' and ansible_os_family != 'Alpine' + when: + - "ansible_system == 'Linux'" + - "ansible_os_family != 'Alpine'" + - '"systemctl" not in shutdown_result["shutdown_command"]' + - '"systemctl" not in shutdown_result_minus["shutdown_command"]' - name: Verify shutdown delay is present in minutes in Void, MacOSX, OpenBSD assert: @@ -68,8 +77,8 @@ - name: Verify shutdown delay is present in seconds in FreeBSD assert: that: - - '"-h +100s" in shutdown_result["shutdown_command"]' - - '"-h +0s" in shutdown_result_minus["shutdown_command"]' + - '"-p +100s" in shutdown_result["shutdown_command"]' + - '"-p +0s" in shutdown_result_minus["shutdown_command"]' when: ansible_system == 'FreeBSD' - name: Verify shutdown delay is present in seconds in Solaris, SunOS @@ -86,8 +95,30 @@ - '"-d 0" in shutdown_result_minus["shutdown_command"]' when: ansible_system == 'VMKernel' -- name: Remove systemd-sysv in ubuntu 18 in case it has been installed in test +- name: Ensure that systemd-sysv is absent in Ubuntu 18 and Debian apt: - name: systemd-sysv + name: sytemd-sysv state: absent - when: systemd_sysv_install is changed + when: (ansible_distribution == 'Ubuntu' and ansible_distribution_major_version is version('18', '>=')) or (ansible_distribution == 'Debian') + register: systemd_sysv_install + +- name: Gather package facts + package_facts: + manager: apt + when: (ansible_distribution == 'Ubuntu' and ansible_distribution_major_version is version('18', '>=')) or (ansible_distribution == 'Debian') + +- name: Execute shutdown if no systemd-sysv + community.general.shutdown: + register: shutdown_result + check_mode: true + when: + - "(ansible_distribution == 'Ubuntu' and ansible_distribution_major_version is version('18', '>=')) or (ansible_distribution == 'Debian')" + - '"systemd-sysv" not in ansible_facts.packages' + +- name: Install systemd_sysv in case it has been removed in test + apt: + name: systemd-sysv + state: present + when: + - "(ansible_distribution == 'Ubuntu' and ansible_distribution_major_version is version('18', '>=')) or (ansible_distribution == 'Debian')" + - "systemd_sysv_install is changed" diff --git a/ansible_collections/community/general/tests/integration/targets/snap/meta/main.yml b/ansible_collections/community/general/tests/integration/targets/snap/meta/main.yml index f36427f71..5c4a48a41 100644 --- a/ansible_collections/community/general/tests/integration/targets/snap/meta/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/snap/meta/main.yml @@ -5,3 +5,4 @@ dependencies: - setup_snap + - setup_remote_tmp_dir diff --git a/ansible_collections/community/general/tests/integration/targets/snap/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/snap/tasks/main.yml index 0f24e69f3..2a683617a 100644 --- a/ansible_collections/community/general/tests/integration/targets/snap/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/snap/tasks/main.yml @@ -8,236 +8,18 @@ # 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: Has-snap block +- name: Has-snap include when: has_snap block: - - name: Make sure package is not installed (hello-world) - community.general.snap: - name: hello-world - state: absent - - - name: Install package (hello-world) (check mode) - community.general.snap: - name: hello-world - state: present - register: install_check - check_mode: true - - - name: Install package (hello-world) - community.general.snap: - name: hello-world - state: present - register: install - - - name: Install package again (hello-world) (check mode) - community.general.snap: - name: hello-world - state: present - register: install_again_check - check_mode: true - - - name: Install package again (hello-world) - community.general.snap: - name: hello-world - state: present - register: install_again - - - name: Assert package has been installed just once (hello-world) - assert: - that: - - install is changed - - install_check is changed - - install_again is not changed - - install_again_check is not changed - - - name: Check package has been installed correctly (hello-world) - command: hello-world - environment: - PATH: /snap/bin/ - - - name: Remove package (hello-world) (check mode) - community.general.snap: - name: hello-world - state: absent - register: remove_check - check_mode: true - - - name: Remove package (hello-world) - community.general.snap: - name: hello-world - state: absent - register: remove - - - name: Remove package again (hello-world) (check mode) - community.general.snap: - name: hello-world - state: absent - register: remove_again_check - check_mode: true - - - name: Remove package again (hello-world) - community.general.snap: - name: hello-world - state: absent - register: remove_again - - - name: Assert package has been removed just once (hello-world) - assert: - that: - - remove is changed - - remove_check is changed - - remove_again is not changed - - remove_again_check is not changed - - - name: Make sure package from classic snap is not installed (nvim) - community.general.snap: - name: nvim - state: absent - - - name: Install package from classic snap (nvim) - community.general.snap: - name: nvim - state: present - classic: true - register: classic_install - - # testing classic idempotency - - name: Install package from classic snap again (nvim) - community.general.snap: - name: nvim - state: present - classic: true - register: classic_install_again - - - name: Assert package has been installed just once (nvim) - assert: - that: - - classic_install is changed - - classic_install_again is not changed - - # this is just testing if a package which has been installed - # with true classic can be removed without setting classic to true - - name: Remove package from classic snap without setting classic to true (nvim) - community.general.snap: - name: nvim - state: absent - register: classic_remove_without_true_classic - - - name: Remove package from classic snap with setting classic to true (nvim) - community.general.snap: - name: nvim - state: absent - classic: true - register: classic_remove_with_true_classic - - - name: Assert package has been removed without setting classic to true (nvim) - assert: - that: - - classic_remove_without_true_classic is changed - - classic_remove_with_true_classic is not changed - - - - name: Make sure package is not installed (uhttpd) - community.general.snap: - name: uhttpd - state: absent - - - name: Install package (uhttpd) - community.general.snap: - name: uhttpd - state: present - register: install - - - name: Install package (uhttpd) - community.general.snap: - name: uhttpd - state: present - options: - - "listening-port=8080" - register: install_with_option - - - name: Install package again with option (uhttpd) - community.general.snap: - name: uhttpd - state: present - options: - - "listening-port=8080" - register: install_with_option_again - - - name: Install package again with different options (uhttpd) - community.general.snap: - name: uhttpd - state: present - options: - - "listening-port=8088" - - "document-root-dir=/tmp" - register: install_with_option_changed - - - name: Remove package (uhttpd) - community.general.snap: - name: uhttpd - state: absent - register: remove - - - name: Assert package has been installed with options just once and only changed options trigger a change (uhttpd) - assert: - that: - - install is changed - - install_with_option is changed - - "install_with_option.options_changed[0] == 'uhttpd:listening-port=8080'" - - install_with_option_again is not changed - - install_with_option_changed is changed - - "'uhttpd:listening-port=8088' in install_with_option_changed.options_changed" - - "'uhttpd:document-root-dir=/tmp' in install_with_option_changed.options_changed" - - "'uhttpd:listening-port=8080' not in install_with_option_changed.options_changed" - - remove is changed - - - name: Install two packages at the same time - community.general.snap: - name: - - hello-world - - uhttpd - state: present - register: install_two - - - name: Install two packages at the same time (again) - community.general.snap: - name: - - hello-world - - uhttpd - state: present - register: install_two_again - - - name: Remove packages (hello-world & uhttpd) - community.general.snap: - name: - - hello-world - - uhttpd - state: absent - register: install_two_remove - - - name: Remove packages again (hello-world & uhttpd) - community.general.snap: - name: - - hello-world - - uhttpd - state: absent - register: install_two_remove_again - - - name: Assert installation of two packages - assert: - that: - - install_two is changed - - "'hello-world' in install_two.snaps_installed" - - "'uhttpd' in install_two.snaps_installed" - - install_two.snaps_removed is not defined - - install_two_again is not changed - - install_two_again.snaps_installed is not defined - - install_two_again.snaps_removed is not defined - - install_two_remove is changed - - install_two_again.snaps_installed is not defined - - "'hello-world' in install_two_remove.snaps_removed" - - "'uhttpd' in install_two_remove.snaps_removed" - - install_two_remove_again is not changed - - install_two_remove_again.snaps_installed is not defined - - install_two_remove_again.snaps_removed is not defined + - name: Include test + ansible.builtin.include_tasks: test.yml + # TODO: Find better package to install from a channel - microk8s installation takes multiple minutes, and even removal takes one minute! + # - name: Include test_channel + # ansible.builtin.include_tasks: test_channel.yml + # TODO: Find bettter package to download and install from sources - cider 1.6.0 takes over 35 seconds to install + # - name: Include test_dangerous + # 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 diff --git a/ansible_collections/community/general/tests/integration/targets/snap/tasks/test.yml b/ansible_collections/community/general/tests/integration/targets/snap/tasks/test.yml new file mode 100644 index 000000000..3a77704b3 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/snap/tasks/test.yml @@ -0,0 +1,235 @@ +--- +# 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: Make sure package is not installed (hello-world) + community.general.snap: + name: hello-world + state: absent + +- name: Install package (hello-world) (check mode) + community.general.snap: + name: hello-world + state: present + register: install_check + check_mode: true + +- name: Install package (hello-world) + community.general.snap: + name: hello-world + state: present + register: install + +- name: Install package again (hello-world) (check mode) + community.general.snap: + name: hello-world + state: present + register: install_again_check + check_mode: true + +- name: Install package again (hello-world) + community.general.snap: + name: hello-world + state: present + register: install_again + +- name: Assert package has been installed just once (hello-world) + assert: + that: + - install is changed + - install_check is changed + - install_again is not changed + - install_again_check is not changed + +- name: Check package has been installed correctly (hello-world) + command: hello-world + environment: + PATH: /snap/bin/ + +- name: Remove package (hello-world) (check mode) + community.general.snap: + name: hello-world + state: absent + register: remove_check + check_mode: true + +- name: Remove package (hello-world) + community.general.snap: + name: hello-world + state: absent + register: remove + +- name: Remove package again (hello-world) (check mode) + community.general.snap: + name: hello-world + state: absent + register: remove_again_check + check_mode: true + +- name: Remove package again (hello-world) + community.general.snap: + name: hello-world + state: absent + register: remove_again + +- name: Assert package has been removed just once (hello-world) + assert: + that: + - remove is changed + - remove_check is changed + - remove_again is not changed + - remove_again_check is not changed + +- name: Make sure package from classic snap is not installed (nvim) + community.general.snap: + name: nvim + state: absent + +- name: Install package from classic snap (nvim) + community.general.snap: + name: nvim + state: present + classic: true + register: classic_install + +# testing classic idempotency +- name: Install package from classic snap again (nvim) + community.general.snap: + name: nvim + state: present + classic: true + register: classic_install_again + +- name: Assert package has been installed just once (nvim) + assert: + that: + - classic_install is changed + - classic_install_again is not changed + +# this is just testing if a package which has been installed +# with true classic can be removed without setting classic to true +- name: Remove package from classic snap without setting classic to true (nvim) + community.general.snap: + name: nvim + state: absent + register: classic_remove_without_true_classic + +- name: Remove package from classic snap with setting classic to true (nvim) + community.general.snap: + name: nvim + state: absent + classic: true + register: classic_remove_with_true_classic + +- name: Assert package has been removed without setting classic to true (nvim) + assert: + that: + - classic_remove_without_true_classic is changed + - classic_remove_with_true_classic is not changed + + +- name: Make sure package is not installed (uhttpd) + community.general.snap: + name: uhttpd + state: absent + +- name: Install package (uhttpd) + community.general.snap: + name: uhttpd + state: present + register: install + +- name: Install package (uhttpd) + community.general.snap: + name: uhttpd + state: present + options: + - "listening-port=8080" + register: install_with_option + +- name: Install package again with option (uhttpd) + community.general.snap: + name: uhttpd + state: present + options: + - "listening-port=8080" + register: install_with_option_again + +- name: Install package again with different options (uhttpd) + community.general.snap: + name: uhttpd + state: present + options: + - "listening-port=8088" + - "document-root-dir=/tmp" + register: install_with_option_changed + +- name: Remove package (uhttpd) + community.general.snap: + name: uhttpd + state: absent + register: remove + +- name: Assert package has been installed with options just once and only changed options trigger a change (uhttpd) + assert: + that: + - install is changed + - install_with_option is changed + - "install_with_option.options_changed[0] == 'uhttpd:listening-port=8080'" + - install_with_option_again is not changed + - install_with_option_changed is changed + - "'uhttpd:listening-port=8088' in install_with_option_changed.options_changed" + - "'uhttpd:document-root-dir=/tmp' in install_with_option_changed.options_changed" + - "'uhttpd:listening-port=8080' not in install_with_option_changed.options_changed" + - remove is changed + +- name: Install two packages at the same time + community.general.snap: + name: + - hello-world + - uhttpd + state: present + register: install_two + +- name: Install two packages at the same time (again) + community.general.snap: + name: + - hello-world + - uhttpd + state: present + register: install_two_again + +- name: Remove packages (hello-world & uhttpd) + community.general.snap: + name: + - hello-world + - uhttpd + state: absent + register: install_two_remove + +- name: Remove packages again (hello-world & uhttpd) + community.general.snap: + name: + - hello-world + - uhttpd + state: absent + register: install_two_remove_again + +- name: Assert installation of two packages + assert: + that: + - install_two is changed + - "'hello-world' in install_two.snaps_installed" + - "'uhttpd' in install_two.snaps_installed" + - install_two.snaps_removed is not defined + - install_two_again is not changed + - install_two_again.snaps_installed is not defined + - install_two_again.snaps_removed is not defined + - install_two_remove is changed + - install_two_again.snaps_installed is not defined + - "'hello-world' in install_two_remove.snaps_removed" + - "'uhttpd' in install_two_remove.snaps_removed" + - install_two_remove_again is not changed + - install_two_remove_again.snaps_installed is not defined + - install_two_remove_again.snaps_removed is not defined diff --git a/ansible_collections/community/general/tests/integration/targets/snap/tasks/test_3dash.yml b/ansible_collections/community/general/tests/integration/targets/snap/tasks/test_3dash.yml new file mode 100644 index 000000000..4d39c0a48 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/snap/tasks/test_3dash.yml @@ -0,0 +1,31 @@ +--- +# 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: Make sure packages are not installed (3 dashes) + community.general.snap: + name: + - bw + - shellcheck + state: absent + +- name: Install package with 3 dashes in description (check) + community.general.snap: + name: + - bw + - shellcheck + state: present + check_mode: true + register: install_3dash_check + +- name: Remove packages (3 dashes) + community.general.snap: + name: + - bw + - shellcheck + state: absent + +- assert: + that: + - install_3dash_check is changed diff --git a/ansible_collections/community/general/tests/integration/targets/snap/tasks/test_channel.yml b/ansible_collections/community/general/tests/integration/targets/snap/tasks/test_channel.yml new file mode 100644 index 000000000..e9eb19c89 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/snap/tasks/test_channel.yml @@ -0,0 +1,81 @@ +--- +# 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 + +# NOTE This is currently disabled for performance reasons! + +- name: Make sure package is not installed (microk8s) + community.general.snap: + name: microk8s + state: absent + +# Test for https://github.com/ansible-collections/community.general/issues/1606 +- name: Install package (microk8s) + community.general.snap: + name: microk8s + classic: true + state: present + register: install_microk8s + +- name: Install package with channel (microk8s) + community.general.snap: + name: microk8s + classic: true + channel: 1.20/stable + state: present + register: install_microk8s_chan + +- name: Install package with channel (microk8s) again + community.general.snap: + name: microk8s + classic: true + channel: 1.20/stable + state: present + register: install_microk8s_chan_again + +- name: Remove package (microk8s) + community.general.snap: + name: microk8s + state: absent + register: remove_microk8s + +- assert: + that: + - install_microk8s is changed + - install_microk8s_chan is changed + - install_microk8s_chan_again is not changed + - remove_microk8s is changed + +- name: Install package (shellcheck) + community.general.snap: + name: shellcheck + state: present + register: install_shellcheck + +- name: Install package with channel (shellcheck) + community.general.snap: + name: shellcheck + channel: edge + state: present + register: install_shellcheck_chan + +- name: Install package with channel (shellcheck) again + community.general.snap: + name: shellcheck + channel: edge + state: present + register: install_shellcheck_chan_again + +- name: Remove package (shellcheck) + community.general.snap: + name: shellcheck + state: absent + register: remove_shellcheck + +- assert: + that: + - install_shellcheck is changed + - install_shellcheck_chan is changed + - install_shellcheck_chan_again is not changed + - remove_shellcheck is changed diff --git a/ansible_collections/community/general/tests/integration/targets/snap/tasks/test_dangerous.yml b/ansible_collections/community/general/tests/integration/targets/snap/tasks/test_dangerous.yml new file mode 100644 index 000000000..8fe4edee0 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/snap/tasks/test_dangerous.yml @@ -0,0 +1,53 @@ +--- +# 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 + +# NOTE This is currently disabled for performance reasons! + +- name: Make sure package is not installed (cider) + community.general.snap: + name: cider + state: absent + +- name: Download cider snap + ansible.builtin.get_url: + url: https://github.com/ciderapp/cider-releases/releases/download/v1.6.0/cider_1.6.0_amd64.snap + dest: "{{ remote_tmp_dir }}/cider_1.6.0_amd64.snap" + mode: "0644" + +# Test for https://github.com/ansible-collections/community.general/issues/5715 +- name: Install package from file (check) + community.general.snap: + name: "{{ remote_tmp_dir }}/cider_1.6.0_amd64.snap" + dangerous: true + state: present + check_mode: true + register: install_dangerous_check + +- name: Install package from file + community.general.snap: + name: "{{ remote_tmp_dir }}/cider_1.6.0_amd64.snap" + dangerous: true + state: present + register: install_dangerous + +- name: Install package from file + community.general.snap: + name: "{{ remote_tmp_dir }}/cider_1.6.0_amd64.snap" + dangerous: true + state: present + register: install_dangerous_idempot + +- name: Remove package + community.general.snap: + name: cider + state: absent + register: remove_dangerous + +- assert: + that: + - install_dangerous_check is changed + - install_dangerous is changed + - install_dangerous_idempot is not changed + - remove_dangerous is changed diff --git a/ansible_collections/community/general/tests/integration/targets/snap/tasks/test_empty_list.yml b/ansible_collections/community/general/tests/integration/targets/snap/tasks/test_empty_list.yml new file mode 100644 index 000000000..bea73cb5e --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/snap/tasks/test_empty_list.yml @@ -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 diff --git a/ansible_collections/community/general/tests/integration/targets/ssh_config/aliases b/ansible_collections/community/general/tests/integration/targets/ssh_config/aliases index 6011128da..2c5cf3237 100644 --- a/ansible_collections/community/general/tests/integration/targets/ssh_config/aliases +++ b/ansible_collections/community/general/tests/integration/targets/ssh_config/aliases @@ -4,6 +4,5 @@ azp/posix/2 destructive -skip/python2.6 # stromssh only supports python3 skip/python2.7 # stromssh only supports python3 skip/freebsd # stromssh installation fails on freebsd diff --git a/ansible_collections/community/general/tests/integration/targets/ssh_config/tasks/options.yml b/ansible_collections/community/general/tests/integration/targets/ssh_config/tasks/options.yml index 406de6831..f88f99081 100644 --- a/ansible_collections/community/general/tests/integration/targets/ssh_config/tasks/options.yml +++ b/ansible_collections/community/general/tests/integration/targets/ssh_config/tasks/options.yml @@ -15,7 +15,12 @@ host: "options.example.com" proxycommand: "ssh jumphost.example.com -W %h:%p" forward_agent: true + add_keys_to_agent: true host_key_algorithms: "+ssh-rsa" + identities_only: true + controlmaster: "auto" + controlpath: "~/.ssh/sockets/%r@%h-%p" + controlpersist: yes state: present register: options_add check_mode: true @@ -33,7 +38,7 @@ src: "{{ ssh_config_test }}" register: slurp_ssh_config -- name: "Options - Verify that nothign was added to {{ ssh_config_test }} during change mode" +- name: "Options - Verify that nothing was added to {{ ssh_config_test }} during change mode" assert: that: - "'options.example.com' not in slurp_ssh_config['content'] | b64decode" @@ -44,7 +49,12 @@ host: "options.example.com" proxycommand: "ssh jumphost.example.com -W %h:%p" forward_agent: true + add_keys_to_agent: true host_key_algorithms: "+ssh-rsa" + identities_only: true + controlmaster: "auto" + controlpath: "~/.ssh/sockets/%r@%h-%p" + controlpersist: yes state: present register: options_add @@ -62,7 +72,12 @@ host: "options.example.com" proxycommand: "ssh jumphost.example.com -W %h:%p" forward_agent: true + add_keys_to_agent: true host_key_algorithms: "+ssh-rsa" + identities_only: true + controlmaster: "auto" + controlpath: "~/.ssh/sockets/%r@%h-%p" + controlpersist: yes state: present register: options_add_again @@ -84,7 +99,12 @@ that: - "'proxycommand ssh jumphost.example.com -W %h:%p' in slurp_ssh_config['content'] | b64decode" - "'forwardagent yes' in slurp_ssh_config['content'] | b64decode" + - "'addkeystoagent yes' in slurp_ssh_config['content'] | b64decode" - "'hostkeyalgorithms +ssh-rsa' in slurp_ssh_config['content'] | b64decode" + - "'identitiesonly yes' in slurp_ssh_config['content'] | b64decode" + - "'controlmaster auto' in slurp_ssh_config['content'] | b64decode" + - "'controlpath ~/.ssh/sockets/%r@%h-%p' in slurp_ssh_config['content'] | b64decode" + - "'controlpersist yes' in slurp_ssh_config['content'] | b64decode" - name: Options - Update host community.general.ssh_config: @@ -92,7 +112,12 @@ host: "options.example.com" proxycommand: "ssh new-jumphost.example.com -W %h:%p" forward_agent: false + add_keys_to_agent: false host_key_algorithms: "+ssh-ed25519" + identities_only: false + controlmaster: no + controlpath: "~/.ssh/new-sockets/%r@%h-%p" + controlpersist: "600" state: present register: options_update @@ -112,7 +137,12 @@ host: "options.example.com" proxycommand: "ssh new-jumphost.example.com -W %h:%p" forward_agent: false + add_keys_to_agent: false host_key_algorithms: "+ssh-ed25519" + identities_only: false + controlmaster: no + controlpath: "~/.ssh/new-sockets/%r@%h-%p" + controlpersist: "600" state: present register: options_update @@ -135,7 +165,12 @@ that: - "'proxycommand ssh new-jumphost.example.com -W %h:%p' in slurp_ssh_config['content'] | b64decode" - "'forwardagent no' in slurp_ssh_config['content'] | b64decode" + - "'addkeystoagent no' in slurp_ssh_config['content'] | b64decode" - "'hostkeyalgorithms +ssh-ed25519' in slurp_ssh_config['content'] | b64decode" + - "'identitiesonly no' in slurp_ssh_config['content'] | b64decode" + - "'controlmaster no' in slurp_ssh_config['content'] | b64decode" + - "'controlpath ~/.ssh/new-sockets/%r@%h-%p' in slurp_ssh_config['content'] | b64decode" + - "'controlpersist 600' in slurp_ssh_config['content'] | b64decode" - name: Options - Ensure no update in case option exist in ssh_config file but wasn't defined in playbook community.general.ssh_config: @@ -163,7 +198,12 @@ that: - "'proxycommand ssh new-jumphost.example.com -W %h:%p' in slurp_ssh_config['content'] | b64decode" - "'forwardagent no' in slurp_ssh_config['content'] | b64decode" + - "'addkeystoagent no' in slurp_ssh_config['content'] | b64decode" - "'hostkeyalgorithms +ssh-ed25519' in slurp_ssh_config['content'] | b64decode" + - "'identitiesonly no' in slurp_ssh_config['content'] | b64decode" + - "'controlmaster no' in slurp_ssh_config['content'] | b64decode" + - "'controlpath ~/.ssh/new-sockets/%r@%h-%p' in slurp_ssh_config['content'] | b64decode" + - "'controlpersist 600' in slurp_ssh_config['content'] | b64decode" - name: Debug debug: @@ -209,7 +249,12 @@ that: - "'proxycommand ssh new-jumphost.example.com -W %h:%p' not in slurp_ssh_config['content'] | b64decode" - "'forwardagent no' not in slurp_ssh_config['content'] | b64decode" + - "'addkeystoagent no' not in slurp_ssh_config['content'] | b64decode" - "'hostkeyalgorithms +ssh-ed25519' not in slurp_ssh_config['content'] | b64decode" + - "'identitiesonly no' not in slurp_ssh_config['content'] | b64decode" + - "'controlmaster auto' not in slurp_ssh_config['content'] | b64decode" + - "'controlpath ~/.ssh/sockets/%r@%h-%p' not in slurp_ssh_config['content'] | b64decode" + - "'controlpersist yes' not in slurp_ssh_config['content'] | b64decode" # Proxycommand and ProxyJump are mutually exclusive. # Reset ssh_config before testing options with proxyjump @@ -225,7 +270,12 @@ host: "options.example.com" proxyjump: "jumphost.example.com" forward_agent: true + add_keys_to_agent: true host_key_algorithms: "+ssh-rsa" + identities_only: true + controlmaster: "auto" + controlpath: "~/.ssh/sockets/%r@%h-%p" + controlpersist: yes state: present register: options_add check_mode: true @@ -243,7 +293,7 @@ src: "{{ ssh_config_test }}" register: slurp_ssh_config -- name: "Options - Verify that nothign was added to {{ ssh_config_test }} during change mode" +- name: "Options - Verify that nothing was added to {{ ssh_config_test }} during change mode" assert: that: - "'options.example.com' not in slurp_ssh_config['content'] | b64decode" @@ -254,7 +304,12 @@ host: "options.example.com" proxyjump: "jumphost.example.com" forward_agent: true + add_keys_to_agent: true host_key_algorithms: "+ssh-rsa" + identities_only: true + controlmaster: "auto" + controlpath: "~/.ssh/sockets/%r@%h-%p" + controlpersist: yes state: present register: options_add @@ -272,7 +327,12 @@ host: "options.example.com" proxyjump: "jumphost.example.com" forward_agent: true + add_keys_to_agent: true host_key_algorithms: "+ssh-rsa" + identities_only: true + controlmaster: "auto" + controlpath: "~/.ssh/sockets/%r@%h-%p" + controlpersist: yes state: present register: options_add_again @@ -294,7 +354,12 @@ that: - "'proxyjump jumphost.example.com' in slurp_ssh_config['content'] | b64decode" - "'forwardagent yes' in slurp_ssh_config['content'] | b64decode" + - "'addkeystoagent yes' in slurp_ssh_config['content'] | b64decode" - "'hostkeyalgorithms +ssh-rsa' in slurp_ssh_config['content'] | b64decode" + - "'identitiesonly yes' in slurp_ssh_config['content'] | b64decode" + - "'controlmaster auto' in slurp_ssh_config['content'] | b64decode" + - "'controlpath ~/.ssh/sockets/%r@%h-%p' in slurp_ssh_config['content'] | b64decode" + - "'controlpersist yes' in slurp_ssh_config['content'] | b64decode" - name: Options - Update host community.general.ssh_config: @@ -302,7 +367,12 @@ host: "options.example.com" proxyjump: "new-jumphost.example.com" forward_agent: false + add_keys_to_agent: false host_key_algorithms: "+ssh-ed25519" + identities_only: false + controlmaster: no + controlpath: "~/.ssh/new-sockets/%r@%h-%p" + controlpersist: "600" state: present register: options_update @@ -322,7 +392,12 @@ host: "options.example.com" proxyjump: "new-jumphost.example.com" forward_agent: false + add_keys_to_agent: false host_key_algorithms: "+ssh-ed25519" + identities_only: false + controlmaster: no + controlpath: "~/.ssh/new-sockets/%r@%h-%p" + controlpersist: "600" state: present register: options_update @@ -345,7 +420,12 @@ that: - "'proxyjump new-jumphost.example.com' in slurp_ssh_config['content'] | b64decode" - "'forwardagent no' in slurp_ssh_config['content'] | b64decode" + - "'addkeystoagent no' in slurp_ssh_config['content'] | b64decode" - "'hostkeyalgorithms +ssh-ed25519' in slurp_ssh_config['content'] | b64decode" + - "'identitiesonly no' in slurp_ssh_config['content'] | b64decode" + - "'controlmaster no' in slurp_ssh_config['content'] | b64decode" + - "'controlpath ~/.ssh/new-sockets/%r@%h-%p' in slurp_ssh_config['content'] | b64decode" + - "'controlpersist 600' in slurp_ssh_config['content'] | b64decode" - name: Options - Ensure no update in case option exist in ssh_config file but wasn't defined in playbook community.general.ssh_config: @@ -373,7 +453,12 @@ that: - "'proxyjump new-jumphost.example.com' in slurp_ssh_config['content'] | b64decode" - "'forwardagent no' in slurp_ssh_config['content'] | b64decode" + - "'addkeystoagent no' in slurp_ssh_config['content'] | b64decode" - "'hostkeyalgorithms +ssh-ed25519' in slurp_ssh_config['content'] | b64decode" + - "'identitiesonly no' in slurp_ssh_config['content'] | b64decode" + - "'controlmaster no' in slurp_ssh_config['content'] | b64decode" + - "'controlpath ~/.ssh/new-sockets/%r@%h-%p' in slurp_ssh_config['content'] | b64decode" + - "'controlpersist 600' in slurp_ssh_config['content'] | b64decode" - name: Debug debug: @@ -419,4 +504,9 @@ that: - "'proxyjump new-jumphost.example.com' not in slurp_ssh_config['content'] | b64decode" - "'forwardagent no' not in slurp_ssh_config['content'] | b64decode" + - "'addkeystoagent no' not in slurp_ssh_config['content'] | b64decode" - "'hostkeyalgorithms +ssh-ed25519' not in slurp_ssh_config['content'] | b64decode" + - "'identitiesonly no' not in slurp_ssh_config['content'] | b64decode" + - "'controlmaster auto' not in slurp_ssh_config['content'] | b64decode" + - "'controlpath ~/.ssh/sockets/%r@%h-%p' not in slurp_ssh_config['content'] | b64decode" + - "'controlpersist yes' not in slurp_ssh_config['content'] | b64decode" diff --git a/ansible_collections/community/general/tests/integration/targets/sudoers/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/sudoers/tasks/main.yml index dd62025d5..36397f41a 100644 --- a/ansible_collections/community/general/tests/integration/targets/sudoers/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/sudoers/tasks/main.yml @@ -159,6 +159,20 @@ src: "{{ sudoers_path }}/my-sudo-rule-8" register: rule_8_contents +- name: Create rule with noexec parameters + community.general.sudoers: + name: my-sudo-rule-9 + state: present + user: alice + commands: /usr/local/bin/command + noexec: true + register: rule_9 + +- name: Grab contents of my-sudo-rule-9 + ansible.builtin.slurp: + src: "{{ sudoers_path }}/my-sudo-rule-9" + register: rule_9_contents + - name: Revoke rule 1 community.general.sudoers: name: my-sudo-rule-1 @@ -257,6 +271,7 @@ - "rule_6_contents['content'] | b64decode == 'alice ALL=(bob)NOPASSWD: /usr/local/bin/command\n'" - "rule_7_contents['content'] | b64decode == 'alice host-1=NOPASSWD: /usr/local/bin/command\n'" - "rule_8_contents['content'] | b64decode == 'alice ALL=NOPASSWD:SETENV: /usr/local/bin/command\n'" + - "rule_9_contents['content'] | b64decode == 'alice ALL=NOEXEC:NOPASSWD: /usr/local/bin/command\n'" - name: Check revocation stat ansible.builtin.assert: diff --git a/ansible_collections/community/general/tests/integration/targets/sysrc/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/sysrc/tasks/main.yml index 2c45c3b1c..ace38202f 100644 --- a/ansible_collections/community/general/tests/integration/targets/sysrc/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/sysrc/tasks/main.yml @@ -140,11 +140,11 @@ - name: Test within jail # # NOTE: currently fails with FreeBSD 12 with minor version less than 4 - # NOTE: currently fails with FreeBSD 13 with minor version less than 1 + # NOTE: currently fails with FreeBSD 13 with minor version less than 2 # when: >- ansible_distribution_version is version('12.4', '>=') and ansible_distribution_version is version('13', '<') - or ansible_distribution_version is version('13.1', '>=') + or ansible_distribution_version is version('13.2', '>=') block: - name: Setup testjail include_tasks: setup-testjail.yml diff --git a/ansible_collections/community/general/tests/integration/targets/terraform/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/terraform/tasks/main.yml index 1c66990be..d04757d8e 100644 --- a/ansible_collections/community/general/tests/integration/targets/terraform/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/terraform/tasks/main.yml @@ -63,5 +63,5 @@ loop_control: index_var: provider_index -- name: Test Complex Varibles +- name: Test Complex Variables ansible.builtin.include_tasks: complex_variables.yml diff --git a/ansible_collections/community/general/tests/integration/targets/test_fqdn_valid/aliases b/ansible_collections/community/general/tests/integration/targets/test_fqdn_valid/aliases new file mode 100644 index 000000000..12d1d6617 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/test_fqdn_valid/aliases @@ -0,0 +1,5 @@ +# 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 diff --git a/ansible_collections/community/general/tests/integration/targets/test_fqdn_valid/runme.sh b/ansible_collections/community/general/tests/integration/targets/test_fqdn_valid/runme.sh new file mode 100755 index 000000000..cd97ac949 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/test_fqdn_valid/runme.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# 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 + +set -eux + +source virtualenv.sh + +# Requirements have to be installed prior to running ansible-playbook +# because plugins and requirements are loaded before the task runs + +pip install fqdn + +ANSIBLE_ROLES_PATH=../ ansible-playbook runme.yml "$@" diff --git a/ansible_collections/community/general/tests/integration/targets/test_fqdn_valid/runme.yml b/ansible_collections/community/general/tests/integration/targets/test_fqdn_valid/runme.yml new file mode 100644 index 000000000..37f965b28 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/test_fqdn_valid/runme.yml @@ -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 + +- hosts: localhost + roles: + - {role: test_fqdn_valid} diff --git a/ansible_collections/community/general/tests/integration/targets/test_fqdn_valid/tasks/fqdn_valid_1.yml b/ansible_collections/community/general/tests/integration/targets/test_fqdn_valid/tasks/fqdn_valid_1.yml new file mode 100644 index 000000000..36cfffad0 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/test_fqdn_valid/tasks/fqdn_valid_1.yml @@ -0,0 +1,58 @@ +--- +# 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: Debug ansible_version + ansible.builtin.debug: + var: ansible_version + when: debug_test|d(false)|bool + tags: t0 + +- name: 1. Test valid hostnames. Default options. + block: + - name: "1. Default min_labels=1, allow_underscores=False" + ansible.builtin.debug: + msg: "hosts_invalid: {{ hosts_invalid }}" + when: debug_test|d(false)|bool + - name: Assert + ansible.builtin.assert: + that: hosts_invalid|difference(result)|length == 0 + vars: + hosts_valid: "{{ names1|select('community.general.fqdn_valid') }}" + hosts_invalid: "{{ names1|difference(hosts_valid) }}" + result: [-rv.example.com, -rv, s_v] + tags: t1 + +- name: 2. Test valid hostnames. allow_underscores=True + block: + - name: "2. allow_underscores=True, default min_labels=1" + ansible.builtin.debug: + msg: "hosts_invalid: {{ hosts_invalid }}" + when: debug_test|d(false)|bool + - name: Assert + ansible.builtin.assert: + that: hosts_invalid|difference(result)|length == 0 + vars: + hosts_valid: "{{ names2|select('community.general.fqdn_valid', + allow_underscores=True) }}" + hosts_invalid: "{{ names2|difference(hosts_valid) }}" + result: [-rv] + tags: t2 + +- name: 3. Test valid hostnames. min_labels=2, allow_underscores=True + block: + - name: "3. allow_underscores=True, min_labels=2" + ansible.builtin.debug: + msg: "hosts_invalid: {{ hosts_invalid }}" + when: debug_test|d(false)|bool + - name: Assert + ansible.builtin.assert: + that: hosts_invalid|difference(result)|length == 0 + vars: + hosts_valid: "{{ names3|select('community.general.fqdn_valid', + min_labels=2, + allow_underscores=True) }}" + hosts_invalid: "{{ names3|difference(hosts_valid) }}" + result: [9rv, s_v-.x.y] + tags: t3 diff --git a/ansible_collections/community/general/tests/integration/targets/test_fqdn_valid/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/test_fqdn_valid/tasks/main.yml new file mode 100644 index 000000000..3bb62555a --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/test_fqdn_valid/tasks/main.yml @@ -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 + +- name: Test fqdn_valid + ansible.builtin.import_tasks: fqdn_valid_1.yml diff --git a/ansible_collections/community/general/tests/integration/targets/test_fqdn_valid/vars/main.yml b/ansible_collections/community/general/tests/integration/targets/test_fqdn_valid/vars/main.yml new file mode 100644 index 000000000..ba0a0eb08 --- /dev/null +++ b/ansible_collections/community/general/tests/integration/targets/test_fqdn_valid/vars/main.yml @@ -0,0 +1,15 @@ +--- +# 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 + +names1: + - srv.example.com + - 9rv.example.com + - -rv.example.com + - srv + - 9rv + - -rv + - s_v +names2: [9rv, -rv, s_v] +names3: [9rv, srv.x, s_v.x.y, s_v-.x.y] diff --git a/ansible_collections/community/general/tests/integration/targets/timezone/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/timezone/tasks/main.yml index 3644eeafa..721341592 100644 --- a/ansible_collections/community/general/tests/integration/targets/timezone/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/timezone/tasks/main.yml @@ -77,6 +77,7 @@ when: - ansible_facts.distribution ~ ansible_facts.distribution_major_version not in ['Fedora31', 'Fedora32'] - not (ansible_os_family == 'Alpine') # TODO + - not (ansible_distribution == 'Archlinux') # TODO block: - name: set timezone to Etc/UTC timezone: @@ -93,4 +94,4 @@ - name: Restore original system timezone - {{ original_timezone.diff.before.name }} timezone: name: "{{ original_timezone.diff.before.name }}" - when: original_timezone is changed + when: original_timezone is changed and original_timezone.diff.before.name != 'n/a' diff --git a/ansible_collections/community/general/tests/integration/targets/ufw/aliases b/ansible_collections/community/general/tests/integration/targets/ufw/aliases index 2ef1a4133..209a1153e 100644 --- a/ansible_collections/community/general/tests/integration/targets/ufw/aliases +++ b/ansible_collections/community/general/tests/integration/targets/ufw/aliases @@ -11,6 +11,8 @@ skip/freebsd skip/rhel8.0 # FIXME skip/rhel9.0 # FIXME skip/rhel9.1 # FIXME +skip/rhel9.2 # FIXME +skip/rhel9.3 # FIXME skip/docker needs/root needs/target/setup_epel diff --git a/ansible_collections/community/general/tests/integration/targets/xml/tasks/main.yml b/ansible_collections/community/general/tests/integration/targets/xml/tasks/main.yml index fe46b3ae5..8235f1a6b 100644 --- a/ansible_collections/community/general/tests/integration/targets/xml/tasks/main.yml +++ b/ansible_collections/community/general/tests/integration/targets/xml/tasks/main.yml @@ -14,6 +14,15 @@ state: present when: ansible_os_family == "FreeBSD" +- name: Install requirements (RHEL 8) + package: + name: + - libxml2-devel + - libxslt-devel + - python3-lxml + state: present + when: ansible_distribution == "RedHat" and ansible_distribution_major_version == "8" + # Needed for MacOSX ! - name: Install lxml pip: diff --git a/ansible_collections/community/general/tests/sanity/extra/botmeta.py b/ansible_collections/community/general/tests/sanity/extra/botmeta.py index 3b6c34834..459d3ba14 100755 --- a/ansible_collections/community/general/tests/sanity/extra/botmeta.py +++ b/ansible_collections/community/general/tests/sanity/extra/botmeta.py @@ -18,6 +18,10 @@ from voluptuous.humanize import humanize_error IGNORE_NO_MAINTAINERS = [ + 'docs/docsite/rst/filter_guide.rst', + 'docs/docsite/rst/filter_guide_abstract_informations.rst', + 'docs/docsite/rst/filter_guide_paths.rst', + 'docs/docsite/rst/filter_guide_selecting_json_data.rst', 'plugins/cache/memcached.py', 'plugins/cache/redis.py', 'plugins/callback/cgroup_memory_recap.py', @@ -197,7 +201,7 @@ def main(): # Scan all files unmatched = set(files) - for dirs in ('plugins', 'tests', 'changelogs'): + for dirs in ('docs/docsite/rst', 'plugins', 'tests', 'changelogs'): for dirpath, dirnames, filenames in os.walk(dirs): for file in sorted(filenames): if file.endswith('.pyc'): diff --git a/ansible_collections/community/general/tests/sanity/extra/extra-docs.py b/ansible_collections/community/general/tests/sanity/extra/extra-docs.py index c636beb08..251e6d70f 100755 --- a/ansible_collections/community/general/tests/sanity/extra/extra-docs.py +++ b/ansible_collections/community/general/tests/sanity/extra/extra-docs.py @@ -17,7 +17,7 @@ def main(): suffix = ':{env}'.format(env=env["ANSIBLE_COLLECTIONS_PATH"]) if 'ANSIBLE_COLLECTIONS_PATH' in env else '' env['ANSIBLE_COLLECTIONS_PATH'] = '{root}{suffix}'.format(root=os.path.dirname(os.path.dirname(os.path.dirname(os.getcwd()))), suffix=suffix) p = subprocess.run( - ['antsibull-docs', 'lint-collection-docs', '--plugin-docs', '--disallow-semantic-markup', '--skip-rstcheck', '.'], + ['antsibull-docs', 'lint-collection-docs', '--plugin-docs', '--skip-rstcheck', '.'], env=env, check=False, ) diff --git a/ansible_collections/community/general/tests/sanity/ignore-2.11.txt b/ansible_collections/community/general/tests/sanity/ignore-2.11.txt deleted file mode 100644 index f2c30270c..000000000 --- a/ansible_collections/community/general/tests/sanity/ignore-2.11.txt +++ /dev/null @@ -1,28 +0,0 @@ -.azure-pipelines/scripts/publish-codecov.py compile-2.6!skip # Uses Python 3.6+ syntax -.azure-pipelines/scripts/publish-codecov.py compile-2.7!skip # Uses Python 3.6+ syntax -.azure-pipelines/scripts/publish-codecov.py compile-3.5!skip # Uses Python 3.6+ syntax -.azure-pipelines/scripts/publish-codecov.py future-import-boilerplate -.azure-pipelines/scripts/publish-codecov.py metaclass-boilerplate -.azure-pipelines/scripts/publish-codecov.py replace-urlopen -plugins/modules/consul.py validate-modules:doc-missing-type -plugins/modules/consul.py validate-modules:undocumented-parameter -plugins/modules/consul_session.py validate-modules:parameter-state-invalid-choice -plugins/modules/gconftool2.py validate-modules:parameter-state-invalid-choice # state=get - removed in 8.0.0 -plugins/modules/iptables_state.py validate-modules:undocumented-parameter # params _back and _timeout used by action plugin -plugins/modules/lxc_container.py validate-modules:use-run-command-not-popen -plugins/modules/manageiq_policies.py validate-modules:parameter-state-invalid-choice # state=list - removed in 8.0.0 -plugins/modules/manageiq_provider.py validate-modules:doc-choices-do-not-match-spec # missing docs on suboptions -plugins/modules/manageiq_provider.py validate-modules:doc-missing-type # missing docs on suboptions -plugins/modules/manageiq_provider.py validate-modules:parameter-type-not-in-doc # missing docs on suboptions -plugins/modules/manageiq_provider.py validate-modules:undocumented-parameter # missing docs on suboptions -plugins/modules/manageiq_tags.py validate-modules:parameter-state-invalid-choice -plugins/modules/osx_defaults.py validate-modules:parameter-state-invalid-choice -plugins/modules/parted.py validate-modules:parameter-state-invalid-choice -plugins/modules/puppet.py validate-modules:parameter-invalid # invalid alias - removed in 7.0.0 -plugins/modules/rax_files_objects.py use-argspec-type-path -plugins/modules/rax_files.py validate-modules:parameter-state-invalid-choice -plugins/modules/rax.py use-argspec-type-path # fix needed -plugins/modules/rhevm.py validate-modules:parameter-state-invalid-choice -plugins/modules/xfconf.py validate-modules:return-syntax-error -tests/integration/targets/django_manage/files/base_test/simple_project/p1/manage.py compile-2.6 # django generated code -tests/integration/targets/django_manage/files/base_test/simple_project/p1/manage.py compile-2.7 # django generated code diff --git a/ansible_collections/community/general/tests/sanity/ignore-2.12.txt b/ansible_collections/community/general/tests/sanity/ignore-2.12.txt deleted file mode 100644 index a8e04ff30..000000000 --- a/ansible_collections/community/general/tests/sanity/ignore-2.12.txt +++ /dev/null @@ -1,21 +0,0 @@ -.azure-pipelines/scripts/publish-codecov.py replace-urlopen -plugins/modules/consul.py validate-modules:doc-missing-type -plugins/modules/consul.py validate-modules:undocumented-parameter -plugins/modules/consul_session.py validate-modules:parameter-state-invalid-choice -plugins/modules/gconftool2.py validate-modules:parameter-state-invalid-choice # state=get - removed in 8.0.0 -plugins/modules/iptables_state.py validate-modules:undocumented-parameter # params _back and _timeout used by action plugin -plugins/modules/lxc_container.py validate-modules:use-run-command-not-popen -plugins/modules/manageiq_policies.py validate-modules:parameter-state-invalid-choice # state=list - removed in 8.0.0 -plugins/modules/manageiq_provider.py validate-modules:doc-choices-do-not-match-spec # missing docs on suboptions -plugins/modules/manageiq_provider.py validate-modules:doc-missing-type # missing docs on suboptions -plugins/modules/manageiq_provider.py validate-modules:parameter-type-not-in-doc # missing docs on suboptions -plugins/modules/manageiq_provider.py validate-modules:undocumented-parameter # missing docs on suboptions -plugins/modules/manageiq_tags.py validate-modules:parameter-state-invalid-choice -plugins/modules/osx_defaults.py validate-modules:parameter-state-invalid-choice -plugins/modules/parted.py validate-modules:parameter-state-invalid-choice -plugins/modules/puppet.py validate-modules:parameter-invalid # invalid alias - removed in 7.0.0 -plugins/modules/rax_files_objects.py use-argspec-type-path -plugins/modules/rax_files.py validate-modules:parameter-state-invalid-choice -plugins/modules/rax.py use-argspec-type-path # fix needed -plugins/modules/rhevm.py validate-modules:parameter-state-invalid-choice -plugins/modules/xfconf.py validate-modules:return-syntax-error diff --git a/ansible_collections/community/general/tests/sanity/ignore-2.13.txt b/ansible_collections/community/general/tests/sanity/ignore-2.13.txt index a8e04ff30..0665ddc1a 100644 --- a/ansible_collections/community/general/tests/sanity/ignore-2.13.txt +++ b/ansible_collections/community/general/tests/sanity/ignore-2.13.txt @@ -1,21 +1,15 @@ .azure-pipelines/scripts/publish-codecov.py replace-urlopen -plugins/modules/consul.py validate-modules:doc-missing-type -plugins/modules/consul.py validate-modules:undocumented-parameter +plugins/lookup/etcd.py validate-modules:invalid-documentation +plugins/lookup/etcd3.py validate-modules:invalid-documentation plugins/modules/consul_session.py validate-modules:parameter-state-invalid-choice -plugins/modules/gconftool2.py validate-modules:parameter-state-invalid-choice # state=get - removed in 8.0.0 plugins/modules/iptables_state.py validate-modules:undocumented-parameter # params _back and _timeout used by action plugin plugins/modules/lxc_container.py validate-modules:use-run-command-not-popen -plugins/modules/manageiq_policies.py validate-modules:parameter-state-invalid-choice # state=list - removed in 8.0.0 -plugins/modules/manageiq_provider.py validate-modules:doc-choices-do-not-match-spec # missing docs on suboptions -plugins/modules/manageiq_provider.py validate-modules:doc-missing-type # missing docs on suboptions -plugins/modules/manageiq_provider.py validate-modules:parameter-type-not-in-doc # missing docs on suboptions -plugins/modules/manageiq_provider.py validate-modules:undocumented-parameter # missing docs on suboptions -plugins/modules/manageiq_tags.py validate-modules:parameter-state-invalid-choice plugins/modules/osx_defaults.py validate-modules:parameter-state-invalid-choice plugins/modules/parted.py validate-modules:parameter-state-invalid-choice -plugins/modules/puppet.py validate-modules:parameter-invalid # invalid alias - removed in 7.0.0 -plugins/modules/rax_files_objects.py use-argspec-type-path -plugins/modules/rax_files.py validate-modules:parameter-state-invalid-choice -plugins/modules/rax.py use-argspec-type-path # fix needed +plugins/modules/rax_files_objects.py use-argspec-type-path # module deprecated - removed in 9.0.0 +plugins/modules/rax_files.py validate-modules:parameter-state-invalid-choice # module deprecated - removed in 9.0.0 +plugins/modules/rax.py use-argspec-type-path # module deprecated - removed in 9.0.0 +plugins/modules/read_csv.py validate-modules:invalid-documentation plugins/modules/rhevm.py validate-modules:parameter-state-invalid-choice plugins/modules/xfconf.py validate-modules:return-syntax-error +tests/unit/plugins/modules/test_gio_mime.yaml no-smart-quotes diff --git a/ansible_collections/community/general/tests/sanity/ignore-2.14.txt b/ansible_collections/community/general/tests/sanity/ignore-2.14.txt index 7e00143a6..fed147e44 100644 --- a/ansible_collections/community/general/tests/sanity/ignore-2.14.txt +++ b/ansible_collections/community/general/tests/sanity/ignore-2.14.txt @@ -1,23 +1,17 @@ .azure-pipelines/scripts/publish-codecov.py replace-urlopen -plugins/modules/consul.py validate-modules:doc-missing-type -plugins/modules/consul.py validate-modules:undocumented-parameter +plugins/lookup/etcd.py validate-modules:invalid-documentation +plugins/lookup/etcd3.py validate-modules:invalid-documentation plugins/modules/consul_session.py validate-modules:parameter-state-invalid-choice -plugins/modules/gconftool2.py validate-modules:parameter-state-invalid-choice # state=get - removed in 8.0.0 plugins/modules/homectl.py import-3.11 # Uses deprecated stdlib library 'crypt' plugins/modules/iptables_state.py validate-modules:undocumented-parameter # params _back and _timeout used by action plugin plugins/modules/lxc_container.py validate-modules:use-run-command-not-popen -plugins/modules/manageiq_policies.py validate-modules:parameter-state-invalid-choice # state=list - removed in 8.0.0 -plugins/modules/manageiq_provider.py validate-modules:doc-choices-do-not-match-spec # missing docs on suboptions -plugins/modules/manageiq_provider.py validate-modules:doc-missing-type # missing docs on suboptions -plugins/modules/manageiq_provider.py validate-modules:parameter-type-not-in-doc # missing docs on suboptions -plugins/modules/manageiq_provider.py validate-modules:undocumented-parameter # missing docs on suboptions -plugins/modules/manageiq_tags.py validate-modules:parameter-state-invalid-choice plugins/modules/osx_defaults.py validate-modules:parameter-state-invalid-choice plugins/modules/parted.py validate-modules:parameter-state-invalid-choice -plugins/modules/puppet.py validate-modules:parameter-invalid # invalid alias - removed in 7.0.0 -plugins/modules/rax_files_objects.py use-argspec-type-path -plugins/modules/rax_files.py validate-modules:parameter-state-invalid-choice -plugins/modules/rax.py use-argspec-type-path # fix needed +plugins/modules/rax_files_objects.py use-argspec-type-path # module deprecated - removed in 9.0.0 +plugins/modules/rax_files.py validate-modules:parameter-state-invalid-choice # module deprecated - removed in 9.0.0 +plugins/modules/rax.py use-argspec-type-path # module deprecated - removed in 9.0.0 +plugins/modules/read_csv.py validate-modules:invalid-documentation plugins/modules/rhevm.py validate-modules:parameter-state-invalid-choice plugins/modules/udm_user.py import-3.11 # Uses deprecated stdlib library 'crypt' plugins/modules/xfconf.py validate-modules:return-syntax-error +tests/unit/plugins/modules/test_gio_mime.yaml no-smart-quotes diff --git a/ansible_collections/community/general/tests/sanity/ignore-2.15.txt b/ansible_collections/community/general/tests/sanity/ignore-2.15.txt index 7e00143a6..d4c92c4d9 100644 --- a/ansible_collections/community/general/tests/sanity/ignore-2.15.txt +++ b/ansible_collections/community/general/tests/sanity/ignore-2.15.txt @@ -1,23 +1,14 @@ .azure-pipelines/scripts/publish-codecov.py replace-urlopen -plugins/modules/consul.py validate-modules:doc-missing-type -plugins/modules/consul.py validate-modules:undocumented-parameter plugins/modules/consul_session.py validate-modules:parameter-state-invalid-choice -plugins/modules/gconftool2.py validate-modules:parameter-state-invalid-choice # state=get - removed in 8.0.0 plugins/modules/homectl.py import-3.11 # Uses deprecated stdlib library 'crypt' plugins/modules/iptables_state.py validate-modules:undocumented-parameter # params _back and _timeout used by action plugin plugins/modules/lxc_container.py validate-modules:use-run-command-not-popen -plugins/modules/manageiq_policies.py validate-modules:parameter-state-invalid-choice # state=list - removed in 8.0.0 -plugins/modules/manageiq_provider.py validate-modules:doc-choices-do-not-match-spec # missing docs on suboptions -plugins/modules/manageiq_provider.py validate-modules:doc-missing-type # missing docs on suboptions -plugins/modules/manageiq_provider.py validate-modules:parameter-type-not-in-doc # missing docs on suboptions -plugins/modules/manageiq_provider.py validate-modules:undocumented-parameter # missing docs on suboptions -plugins/modules/manageiq_tags.py validate-modules:parameter-state-invalid-choice plugins/modules/osx_defaults.py validate-modules:parameter-state-invalid-choice plugins/modules/parted.py validate-modules:parameter-state-invalid-choice -plugins/modules/puppet.py validate-modules:parameter-invalid # invalid alias - removed in 7.0.0 -plugins/modules/rax_files_objects.py use-argspec-type-path -plugins/modules/rax_files.py validate-modules:parameter-state-invalid-choice -plugins/modules/rax.py use-argspec-type-path # fix needed +plugins/modules/rax_files_objects.py use-argspec-type-path # module deprecated - removed in 9.0.0 +plugins/modules/rax_files.py validate-modules:parameter-state-invalid-choice # module deprecated - removed in 9.0.0 +plugins/modules/rax.py use-argspec-type-path # module deprecated - removed in 9.0.0 plugins/modules/rhevm.py validate-modules:parameter-state-invalid-choice plugins/modules/udm_user.py import-3.11 # Uses deprecated stdlib library 'crypt' plugins/modules/xfconf.py validate-modules:return-syntax-error +tests/unit/plugins/modules/test_gio_mime.yaml no-smart-quotes diff --git a/ansible_collections/community/general/tests/sanity/ignore-2.16.txt b/ansible_collections/community/general/tests/sanity/ignore-2.16.txt index 5fa3d90ba..397c6d986 100644 --- a/ansible_collections/community/general/tests/sanity/ignore-2.16.txt +++ b/ansible_collections/community/general/tests/sanity/ignore-2.16.txt @@ -1,23 +1,15 @@ -.azure-pipelines/scripts/publish-codecov.py replace-urlopen -plugins/modules/consul.py validate-modules:doc-missing-type -plugins/modules/consul.py validate-modules:undocumented-parameter plugins/modules/consul_session.py validate-modules:parameter-state-invalid-choice -plugins/modules/gconftool2.py validate-modules:parameter-state-invalid-choice # state=get - removed in 8.0.0 plugins/modules/homectl.py import-3.11 # Uses deprecated stdlib library 'crypt' +plugins/modules/homectl.py import-3.12 # Uses deprecated stdlib library 'crypt' plugins/modules/iptables_state.py validate-modules:undocumented-parameter # params _back and _timeout used by action plugin plugins/modules/lxc_container.py validate-modules:use-run-command-not-popen -plugins/modules/manageiq_policies.py validate-modules:parameter-state-invalid-choice # state=list - removed in 8.0.0 -plugins/modules/manageiq_provider.py validate-modules:doc-choices-do-not-match-spec # missing docs on suboptions -plugins/modules/manageiq_provider.py validate-modules:doc-missing-type # missing docs on suboptions -plugins/modules/manageiq_provider.py validate-modules:parameter-type-not-in-doc # missing docs on suboptions -plugins/modules/manageiq_provider.py validate-modules:undocumented-parameter # missing docs on suboptions -plugins/modules/manageiq_tags.py validate-modules:parameter-state-invalid-choice # state=list - removed in 8.0.0 plugins/modules/osx_defaults.py validate-modules:parameter-state-invalid-choice plugins/modules/parted.py validate-modules:parameter-state-invalid-choice -plugins/modules/puppet.py validate-modules:parameter-invalid # invalid alias - removed in 7.0.0 plugins/modules/rax_files_objects.py use-argspec-type-path # module deprecated - removed in 9.0.0 plugins/modules/rax_files.py validate-modules:parameter-state-invalid-choice # module deprecated - removed in 9.0.0 plugins/modules/rax.py use-argspec-type-path # module deprecated - removed in 9.0.0 plugins/modules/rhevm.py validate-modules:parameter-state-invalid-choice plugins/modules/udm_user.py import-3.11 # Uses deprecated stdlib library 'crypt' +plugins/modules/udm_user.py import-3.12 # Uses deprecated stdlib library 'crypt' plugins/modules/xfconf.py validate-modules:return-syntax-error +tests/unit/plugins/modules/test_gio_mime.yaml no-smart-quotes diff --git a/ansible_collections/community/general/tests/sanity/ignore-2.17.txt b/ansible_collections/community/general/tests/sanity/ignore-2.17.txt new file mode 100644 index 000000000..d75aaeac2 --- /dev/null +++ b/ansible_collections/community/general/tests/sanity/ignore-2.17.txt @@ -0,0 +1,17 @@ +plugins/modules/consul_session.py validate-modules:parameter-state-invalid-choice +plugins/modules/homectl.py import-3.11 # Uses deprecated stdlib library 'crypt' +plugins/modules/homectl.py import-3.12 # Uses deprecated stdlib library 'crypt' +plugins/modules/iptables_state.py validate-modules:undocumented-parameter # params _back and _timeout used by action plugin +plugins/modules/lxc_container.py validate-modules:use-run-command-not-popen +plugins/modules/osx_defaults.py validate-modules:parameter-state-invalid-choice +plugins/modules/parted.py validate-modules:parameter-state-invalid-choice +plugins/modules/rax_files_objects.py use-argspec-type-path # module deprecated - removed in 9.0.0 +plugins/modules/rax_files.py validate-modules:parameter-state-invalid-choice # module deprecated - removed in 9.0.0 +plugins/modules/rax.py use-argspec-type-path # module deprecated - removed in 9.0.0 +plugins/modules/rhevm.py validate-modules:parameter-state-invalid-choice +plugins/modules/udm_user.py import-3.11 # Uses deprecated stdlib library 'crypt' +plugins/modules/udm_user.py import-3.12 # Uses deprecated stdlib library 'crypt' +plugins/modules/xfconf.py validate-modules:return-syntax-error +plugins/module_utils/univention_umc.py pylint:use-yield-from # suggested construct does not work with Python 2 +tests/unit/compat/mock.py pylint:use-yield-from # suggested construct does not work with Python 2 +tests/unit/plugins/modules/test_gio_mime.yaml no-smart-quotes diff --git a/ansible_collections/community/general/tests/sanity/ignore-2.12.txt.license b/ansible_collections/community/general/tests/sanity/ignore-2.17.txt.license index edff8c768..edff8c768 100644 --- a/ansible_collections/community/general/tests/sanity/ignore-2.12.txt.license +++ b/ansible_collections/community/general/tests/sanity/ignore-2.17.txt.license diff --git a/ansible_collections/community/general/tests/unit/mock/loader.py b/ansible_collections/community/general/tests/unit/mock/loader.py index 948f4eecd..f7aff17c3 100644 --- a/ansible_collections/community/general/tests/unit/mock/loader.py +++ b/ansible_collections/community/general/tests/unit/mock/loader.py @@ -17,7 +17,7 @@ class DictDataLoader(DataLoader): def __init__(self, file_mapping=None): file_mapping = {} if file_mapping is None else file_mapping - assert type(file_mapping) == dict + assert isinstance(file_mapping, dict) super(DictDataLoader, self).__init__() diff --git a/ansible_collections/community/general/tests/unit/plugins/become/helper.py b/ansible_collections/community/general/tests/unit/plugins/become/helper.py index 9949e1bef..d2a7df97f 100644 --- a/ansible_collections/community/general/tests/unit/plugins/become/helper.py +++ b/ansible_collections/community/general/tests/unit/plugins/become/helper.py @@ -12,7 +12,7 @@ from ansible.plugins.loader import become_loader, get_shell_plugin def call_become_plugin(task, var_options, cmd, executable=None): - """Helper function to call become plugin simiarly on how Ansible itself handles this.""" + """Helper function to call become plugin similarly on how Ansible itself handles this.""" plugin = become_loader.get(task['become_method']) plugin.set_options(task_keys=task, var_options=var_options) shell = get_shell_plugin(executable=executable) diff --git a/ansible_collections/community/general/tests/unit/plugins/callback/test_loganalytics.py b/ansible_collections/community/general/tests/unit/plugins/callback/test_loganalytics.py index f9fef3c5d..17932ed5f 100644 --- a/ansible_collections/community/general/tests/unit/plugins/callback/test_loganalytics.py +++ b/ansible_collections/community/general/tests/unit/plugins/callback/test_loganalytics.py @@ -12,6 +12,7 @@ from ansible_collections.community.general.plugins.callback.loganalytics import from datetime import datetime import json +import sys class TestAzureLogAnalytics(unittest.TestCase): @@ -27,6 +28,10 @@ class TestAzureLogAnalytics(unittest.TestCase): self.mock_host = Mock('MockHost') self.mock_host.name = 'myhost' + # Add backward compatibility + if sys.version_info < (3, 2): + self.assertRegex = self.assertRegexpMatches + @patch('ansible_collections.community.general.plugins.callback.loganalytics.datetime') @patch('ansible_collections.community.general.plugins.callback.loganalytics.open_url') def test_overall(self, open_url_mock, mock_datetime): @@ -62,5 +67,5 @@ class TestAzureLogAnalytics(unittest.TestCase): args, kwargs = open_url_mock.call_args headers = kwargs['headers'] - self.assertRegexpMatches(headers['Authorization'], r'^SharedKey 01234567-0123-0123-0123-01234567890a:.*=$') + self.assertRegex(headers['Authorization'], r'^SharedKey 01234567-0123-0123-0123-01234567890a:.*=$') self.assertEqual(headers['Log-Type'], 'ansible_playbook') diff --git a/ansible_collections/community/general/tests/unit/plugins/connection/test_lxc.py b/ansible_collections/community/general/tests/unit/plugins/connection/test_lxc.py index 8733a92e0..bebd42772 100644 --- a/ansible_collections/community/general/tests/unit/plugins/connection/test_lxc.py +++ b/ansible_collections/community/general/tests/unit/plugins/connection/test_lxc.py @@ -6,20 +6,138 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type +import pytest +import sys + from io import StringIO -from ansible_collections.community.general.tests.unit.compat import unittest -from ansible_collections.community.general.plugins.connection import lxc +from ansible.errors import AnsibleError from ansible.playbook.play_context import PlayContext +from ansible.plugins.loader import connection_loader +from ansible_collections.community.general.tests.unit.compat import mock + + +@pytest.fixture(autouse=True) +def lxc(request): + """Fixture to import/load the lxc plugin module. + + The fixture parameter is used to determine the presence of liblxc. + When true (default), a mocked liblxc module is injected. If False, + no liblxc will be present. + """ + liblxc_present = getattr(request, 'param', True) + + class ContainerMock(): + # dict of container name to its state + _container_states = {} + + def __init__(self, name): + super(ContainerMock, self).__init__() + self.name = name + + @property + def state(self): + return ContainerMock._container_states.get(self.name, 'STARTED') + + liblxc_module_mock = mock.MagicMock() + liblxc_module_mock.Container = ContainerMock + + with mock.patch.dict('sys.modules'): + if liblxc_present: + sys.modules['lxc'] = liblxc_module_mock + elif 'lxc' in sys.modules: + del sys.modules['lxc'] + + from ansible_collections.community.general.plugins.connection import lxc as lxc_plugin_module + + assert lxc_plugin_module.HAS_LIBLXC == liblxc_present + assert bool(getattr(lxc_plugin_module, '_lxc', None)) == liblxc_present + + yield lxc_plugin_module + + +class TestLXCConnectionClass(): + + @pytest.mark.parametrize('lxc', [True, False], indirect=True) + def test_lxc_connection_module(self, lxc): + """Test that a connection can be created with the plugin.""" + play_context = PlayContext() + in_stream = StringIO() + conn = connection_loader.get('lxc', play_context, in_stream) + assert conn + assert isinstance(conn, lxc.Connection) -class TestLXCConnectionClass(unittest.TestCase): + @pytest.mark.parametrize('lxc', [False], indirect=True) + def test_lxc_connection_liblxc_error(self, lxc): + """Test that on connect an error is thrown if liblxc is not present.""" + play_context = PlayContext() + in_stream = StringIO() + conn = connection_loader.get('lxc', play_context, in_stream) + + with pytest.raises(AnsibleError, match='lxc python bindings are not installed'): + conn._connect() - def test_lxc_connection_module(self): + def test_remote_addr_option(self): + """Test that the remote_addr option is used""" play_context = PlayContext() - play_context.prompt = ( - '[sudo via ansible, key=ouzmdnewuhucvuaabtjmweasarviygqq] password: ' - ) in_stream = StringIO() + conn = connection_loader.get('lxc', play_context, in_stream) + + container_name = 'my-container' + conn.set_option('remote_addr', container_name) + assert conn.get_option('remote_addr') == container_name + + conn._connect() + assert conn.container_name == container_name + + def test_error_when_stopped(self, lxc): + """Test that on connect an error is thrown if the container is stopped.""" + play_context = PlayContext() + in_stream = StringIO() + conn = connection_loader.get('lxc', play_context, in_stream) + conn.set_option('remote_addr', 'my-container') + + lxc._lxc.Container._container_states['my-container'] = 'STOPPED' + + with pytest.raises(AnsibleError, match='my-container is not running'): + conn._connect() + + def test_container_name_change(self): + """Test connect method reconnects when remote_addr changes""" + play_context = PlayContext() + in_stream = StringIO() + conn = connection_loader.get('lxc', play_context, in_stream) + + # setting the option does nothing + container1_name = 'my-container' + conn.set_option('remote_addr', container1_name) + assert conn.container_name is None + assert conn.container is None + + # first call initializes the connection + conn._connect() + assert conn.container_name is container1_name + assert conn.container is not None + assert conn.container.name == container1_name + container1 = conn.container + + # second call is basically a no-op + conn._connect() + assert conn.container_name is container1_name + assert conn.container is container1 + assert conn.container.name == container1_name + + # setting the option does again nothing + container2_name = 'my-other-container' + conn.set_option('remote_addr', container2_name) + assert conn.container_name == container1_name + assert conn.container is container1 + assert conn.container.name == container1_name - self.assertIsInstance(lxc.Connection(play_context, in_stream), lxc.Connection) + # first call with a different remote_addr changes the connection + conn._connect() + assert conn.container_name == container2_name + assert conn.container is not None + assert conn.container is not container1 + assert conn.container.name == container2_name diff --git a/ansible_collections/community/general/tests/unit/plugins/inventory/test_icinga2.py b/ansible_collections/community/general/tests/unit/plugins/inventory/test_icinga2.py index e3928b0db..859f29d3b 100644 --- a/ansible_collections/community/general/tests/unit/plugins/inventory/test_icinga2.py +++ b/ansible_collections/community/general/tests/unit/plugins/inventory/test_icinga2.py @@ -86,6 +86,8 @@ def get_option(option): return {} elif option == 'strict': return False + elif option == 'group_by_hostgroups': + return True else: return None @@ -96,6 +98,7 @@ def test_populate(inventory, mocker): inventory.icinga2_password = 'password' inventory.icinga2_url = 'https://localhost:5665' + '/v1' inventory.inventory_attr = "address" + inventory.group_by_hostgroups = True # bypass authentication and API fetch calls inventory._check_api = mocker.MagicMock(side_effect=check_api) diff --git a/ansible_collections/community/general/tests/unit/plugins/inventory/test_linode.py b/ansible_collections/community/general/tests/unit/plugins/inventory/test_linode.py index a4f556761..0f239f2dd 100644 --- a/ansible_collections/community/general/tests/unit/plugins/inventory/test_linode.py +++ b/ansible_collections/community/general/tests/unit/plugins/inventory/test_linode.py @@ -37,11 +37,25 @@ def test_missing_access_token_lookup(inventory): assert 'Could not retrieve Linode access token' in error_message -def test_verify_file(tmp_path, inventory): +def test_verify_file_yml(tmp_path, inventory): file = tmp_path / "foobar.linode.yml" file.touch() assert inventory.verify_file(str(file)) is True +def test_verify_file_yaml(tmp_path, inventory): + file = tmp_path / "foobar.linode.yaml" + file.touch() + assert inventory.verify_file(str(file)) is True + + +def test_verify_file_bad_config_yml(inventory): + assert inventory.verify_file("foobar.linode.yml") is False + + +def test_verify_file_bad_config_yaml(inventory): + assert inventory.verify_file("foobar.linode.yaml") is False + + def test_verify_file_bad_config(inventory): - assert inventory.verify_file('foobar.linode.yml') is False + assert inventory.verify_file("foobar.wrongcloud.yml") is False diff --git a/ansible_collections/community/general/tests/unit/plugins/inventory/test_proxmox.py b/ansible_collections/community/general/tests/unit/plugins/inventory/test_proxmox.py index 13832c938..ea6c84bcd 100644 --- a/ansible_collections/community/general/tests/unit/plugins/inventory/test_proxmox.py +++ b/ansible_collections/community/general/tests/unit/plugins/inventory/test_proxmox.py @@ -646,13 +646,15 @@ def test_populate(inventory, mocker): inventory.group_prefix = 'proxmox_' inventory.facts_prefix = 'proxmox_' inventory.strict = False + inventory.exclude_nodes = False opts = { 'group_prefix': 'proxmox_', 'facts_prefix': 'proxmox_', 'want_facts': True, 'want_proxmox_nodes_ansible_host': True, - 'qemu_extended_statuses': True + 'qemu_extended_statuses': True, + 'exclude_nodes': False } # bypass authentication and API fetch calls @@ -723,13 +725,15 @@ def test_populate_missing_qemu_extended_groups(inventory, mocker): inventory.group_prefix = 'proxmox_' inventory.facts_prefix = 'proxmox_' inventory.strict = False + inventory.exclude_nodes = False opts = { 'group_prefix': 'proxmox_', 'facts_prefix': 'proxmox_', 'want_facts': True, 'want_proxmox_nodes_ansible_host': True, - 'qemu_extended_statuses': False + 'qemu_extended_statuses': False, + 'exclude_nodes': False } # bypass authentication and API fetch calls @@ -743,3 +747,40 @@ def test_populate_missing_qemu_extended_groups(inventory, mocker): # make sure that ['prelaunch', 'paused'] are not in the group list for group in ['paused', 'prelaunch']: assert ('%sall_%s' % (inventory.group_prefix, group)) not in inventory.inventory.groups + + +def test_populate_exclude_nodes(inventory, mocker): + # module settings + inventory.proxmox_user = 'root@pam' + inventory.proxmox_password = 'password' + inventory.proxmox_url = 'https://localhost:8006' + inventory.group_prefix = 'proxmox_' + inventory.facts_prefix = 'proxmox_' + inventory.strict = False + inventory.exclude_nodes = True + + opts = { + 'group_prefix': 'proxmox_', + 'facts_prefix': 'proxmox_', + 'want_facts': True, + 'want_proxmox_nodes_ansible_host': True, + 'qemu_extended_statuses': False, + 'exclude_nodes': True + } + + # bypass authentication and API fetch calls + inventory._get_auth = mocker.MagicMock(side_effect=get_auth) + inventory._get_json = mocker.MagicMock(side_effect=get_json) + inventory._get_vm_snapshots = mocker.MagicMock(side_effect=get_vm_snapshots) + inventory.get_option = mocker.MagicMock(side_effect=get_option(opts)) + inventory._can_add_host = mocker.MagicMock(return_value=True) + inventory._populate() + + # make sure that nodes are not in the inventory + for node in ['testnode', 'testnode2']: + assert node not in inventory.inventory.hosts + # make sure that nodes group is absent + assert ('%s_nodes' % (inventory.group_prefix)) not in inventory.inventory.groups + # make sure that nodes are not in the "ungrouped" group + for node in ['testnode', 'testnode2']: + assert node not in inventory.inventory.get_groups_dict()["ungrouped"] diff --git a/ansible_collections/community/general/tests/unit/plugins/lookup/onepassword_conftest.py b/ansible_collections/community/general/tests/unit/plugins/lookup/conftest.py index 18afae1a3..d4ae42ab8 100644 --- a/ansible_collections/community/general/tests/unit/plugins/lookup/onepassword_conftest.py +++ b/ansible_collections/community/general/tests/unit/plugins/lookup/conftest.py @@ -10,17 +10,11 @@ import pytest from ansible_collections.community.general.plugins.lookup.onepassword import OnePass -OP_VERSION_FIXTURES = [ - "opv1", - "opv2" -] - - @pytest.fixture def fake_op(mocker): def _fake_op(version): mocker.patch("ansible_collections.community.general.plugins.lookup.onepassword.OnePassCLIBase.get_current_version", return_value=version) - op = OnePass(None, None, None, None, None) + op = OnePass() op._config._config_file_path = "/home/jin/.op/config" mocker.patch.object(op._cli, "_run") diff --git a/ansible_collections/community/general/tests/unit/plugins/lookup/onepassword_common.py b/ansible_collections/community/general/tests/unit/plugins/lookup/onepassword_common.py index 092979225..bf0cc35c1 100644 --- a/ansible_collections/community/general/tests/unit/plugins/lookup/onepassword_common.py +++ b/ansible_collections/community/general/tests/unit/plugins/lookup/onepassword_common.py @@ -81,5 +81,215 @@ MOCK_ENTRIES = { "expected": ["first value"], "output": load_file("v2_out_03.json") }, + { + # Request data from an omitted value (label lookup, no section) + "vault_name": "Test Vault", + "queries": ["Omitted values"], + "kwargs": { + "field": "label-without-value", + }, + "expected": [""], + "output": load_file("v2_out_04.json") + }, + { + # Request data from an omitted value (id lookup, no section) + "vault_name": "Test Vault", + "queries": ["Omitted values"], + "kwargs": { + "field": "67890q7mspf4x6zrlw3qejn7m", + }, + "expected": [""], + "output": load_file("v2_out_04.json") + }, + { + # Request data from an omitted value (label lookup, with section) + "vault_name": "Test Vault", + "queries": ["Omitted values"], + "kwargs": { + "field": "section-label-without-value", + "section": "Section-Without-Values" + }, + "expected": [""], + "output": load_file("v2_out_04.json") + }, + { + # Request data from an omitted value (id lookup, with section) + "vault_name": "Test Vault", + "queries": ["Omitted values"], + "kwargs": { + "field": "123345q7mspf4x6zrlw3qejn7m", + "section": "section-without-values", + }, + "expected": [""], + "output": load_file("v2_out_04.json") + }, + { + # Query item without section by lowercase id (case matching) + "vault_name": "Test Vault", + "queries": ["LabelCasing"], + "kwargs": { + "field": "lowercaseid", + }, + "expected": ["lowercaseid"], + "output": load_file("v2_out_05.json") + }, + { + # Query item without section by lowercase id (case not matching) + "vault_name": "Test Vault", + "queries": ["LabelCasing"], + "kwargs": { + "field": "LOWERCASEID", + }, + "expected": ["lowercaseid"], + "output": load_file("v2_out_05.json") + }, + { + # Query item without section by lowercase label (case matching) + "vault_name": "Test Vault", + "queries": ["LabelCasing"], + "kwargs": { + "field": "lowercaselabel", + }, + "expected": ["lowercaselabel"], + "output": load_file("v2_out_05.json") + }, + { + # Query item without section by lowercase label (case not matching) + "vault_name": "Test Vault", + "queries": ["LabelCasing"], + "kwargs": { + "field": "LOWERCASELABEL", + }, + "expected": ["lowercaselabel"], + "output": load_file("v2_out_05.json") + }, + { + # Query item without section by mixed case id (case matching) + "vault_name": "Test Vault", + "queries": ["LabelCasing"], + "kwargs": { + "field": "MiXeDcAsEiD", + }, + "expected": ["mixedcaseid"], + "output": load_file("v2_out_05.json") + }, + { + # Query item without section by mixed case id (case not matching) + "vault_name": "Test Vault", + "queries": ["LabelCasing"], + "kwargs": { + "field": "mixedcaseid", + }, + "expected": ["mixedcaseid"], + "output": load_file("v2_out_05.json") + }, + { + # Query item without section by mixed case label (case matching) + "vault_name": "Test Vault", + "queries": ["LabelCasing"], + "kwargs": { + "field": "MiXeDcAsElAbEl", + }, + "expected": ["mixedcaselabel"], + "output": load_file("v2_out_05.json") + }, + { + # Query item without section by mixed case label (case not matching) + "vault_name": "Test Vault", + "queries": ["LabelCasing"], + "kwargs": { + "field": "mixedcaselabel", + }, + "expected": ["mixedcaselabel"], + "output": load_file("v2_out_05.json") + }, + { + # Query item with section by lowercase id (case matching) + "vault_name": "Test Vault", + "queries": ["LabelCasing"], + "kwargs": { + "field": "sectionlowercaseid", + "section": "section-with-values", + }, + "expected": ["sectionlowercaseid"], + "output": load_file("v2_out_05.json") + }, + { + # Query item with section by lowercase id (case not matching) + "vault_name": "Test Vault", + "queries": ["LabelCasing"], + "kwargs": { + "field": "SECTIONLOWERCASEID", + "section": "section-with-values", + }, + "expected": ["sectionlowercaseid"], + "output": load_file("v2_out_05.json") + }, + { + # Query item with section by lowercase label (case matching) + "vault_name": "Test Vault", + "queries": ["LabelCasing"], + "kwargs": { + "field": "sectionlowercaselabel", + "section": "section-with-values", + }, + "expected": ["sectionlowercaselabel"], + "output": load_file("v2_out_05.json") + }, + { + # Query item with section by lowercase label (case not matching) + "vault_name": "Test Vault", + "queries": ["LabelCasing"], + "kwargs": { + "field": "SECTIONLOWERCASELABEL", + "section": "section-with-values", + }, + "expected": ["sectionlowercaselabel"], + "output": load_file("v2_out_05.json") + }, + { + # Query item with section by lowercase id (case matching) + "vault_name": "Test Vault", + "queries": ["LabelCasing"], + "kwargs": { + "field": "SeCtIoNmIxEdCaSeId", + "section": "section-with-values", + }, + "expected": ["sectionmixedcaseid"], + "output": load_file("v2_out_05.json") + }, + { + # Query item with section by lowercase id (case not matching) + "vault_name": "Test Vault", + "queries": ["LabelCasing"], + "kwargs": { + "field": "sectionmixedcaseid", + "section": "section-with-values", + }, + "expected": ["sectionmixedcaseid"], + "output": load_file("v2_out_05.json") + }, + { + # Query item with section by lowercase label (case matching) + "vault_name": "Test Vault", + "queries": ["LabelCasing"], + "kwargs": { + "field": "SeCtIoNmIxEdCaSeLaBeL", + "section": "section-with-values", + }, + "expected": ["sectionmixedcaselabel"], + "output": load_file("v2_out_05.json") + }, + { + # Query item with section by lowercase label (case not matching) + "vault_name": "Test Vault", + "queries": ["LabelCasing"], + "kwargs": { + "field": "sectionmixedcaselabel", + "section": "section-with-values", + }, + "expected": ["sectionmixedcaselabel"], + "output": load_file("v2_out_05.json") + }, ], } diff --git a/ansible_collections/community/general/tests/unit/plugins/lookup/onepassword_fixtures/v2_out_01.json b/ansible_collections/community/general/tests/unit/plugins/lookup/onepassword_fixtures/v2_out_01.json index 7ef0bb0c2..0ace5c825 100644 --- a/ansible_collections/community/general/tests/unit/plugins/lookup/onepassword_fixtures/v2_out_01.json +++ b/ansible_collections/community/general/tests/unit/plugins/lookup/onepassword_fixtures/v2_out_01.json @@ -13,10 +13,10 @@ "additional_information": "Jan 18, 2015, 08:13:38", "fields": [ { - "id": "password", + "id": "Password", "type": "CONCEALED", "purpose": "PASSWORD", - "label": "password", + "label": "Password", "value": "OctoberPoppyNuttyDraperySabbath", "reference": "op://Test Vault/Authy Backup/password", "password_details": { diff --git a/ansible_collections/community/general/tests/unit/plugins/lookup/onepassword_fixtures/v2_out_04.json b/ansible_collections/community/general/tests/unit/plugins/lookup/onepassword_fixtures/v2_out_04.json new file mode 100644 index 000000000..13b6cc2aa --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/lookup/onepassword_fixtures/v2_out_04.json @@ -0,0 +1,67 @@ +{ + "id": "bgqegp3xcxnpfkb45olwigpkpi", + "title": "OmittedValues", + "version": 1, + "vault": { + "id": "stpebbaccrq72xulgouxsk4p7y", + "name": "Private" + }, + "category": "LOGIN", + "last_edited_by": "WOUTERRUYBH7BFPHMZ2KKGL6AU", + "created_at": "2023-09-12T08:30:07Z", + "updated_at": "2023-09-12T08:30:07Z", + "additional_information": "fluxility", + "sections": [ + { + "id": "7osqcvd43i75teocdzbb6d7mie", + "label": "section-without-values" + } + ], + "fields": [ + { + "id": "username", + "type": "STRING", + "purpose": "USERNAME", + "label": "username", + "value": "fluxility", + "reference": "op://Testcase/OmittedValues/username" + }, + { + "id": "password", + "type": "CONCEALED", + "purpose": "PASSWORD", + "label": "password", + "value": "j89Dyb7psat*hkbkyLUQyq@GR.a-g2pQH_V_xtMhrn37rQ_2uRYoRiozj6TjWVLy2pbfEvjnse", + "entropy": 427.01202392578125, + "reference": "op://Testcase/OmittedValues/password", + "password_details": { + "entropy": 427, + "generated": true, + "strength": "FANTASTIC" + } + }, + { + "id": "notesPlain", + "type": "STRING", + "purpose": "NOTES", + "label": "notesPlain", + "reference": "op://Testcase/OmittedValues/notesPlain" + }, + { + "id": "67890q7mspf4x6zrlw3qejn7m", + "type": "URL", + "label": "label-without-value", + "reference": "op://01202392578125/OmittedValues/section-without-values/section-without-value" + }, + { + "id": "123345q7mspf4x6zrlw3qejn7m", + "section": { + "id": "6hbtca5yrlmoptgy3nw74222", + "label": "section-without-values" + }, + "type": "URL", + "label": "section-label-without-value", + "reference": "op://01202392578125/OmittedValues/section-without-values/section-without-value" + } + ] +} diff --git a/ansible_collections/community/general/tests/unit/plugins/lookup/onepassword_fixtures/v2_out_04.json.license b/ansible_collections/community/general/tests/unit/plugins/lookup/onepassword_fixtures/v2_out_04.json.license new file mode 100644 index 000000000..969b956c2 --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/lookup/onepassword_fixtures/v2_out_04.json.license @@ -0,0 +1,3 @@ +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 +SPDX-FileCopyrightText: 2022, Ansible Project diff --git a/ansible_collections/community/general/tests/unit/plugins/lookup/onepassword_fixtures/v2_out_05.json b/ansible_collections/community/general/tests/unit/plugins/lookup/onepassword_fixtures/v2_out_05.json new file mode 100644 index 000000000..f925476e1 --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/lookup/onepassword_fixtures/v2_out_05.json @@ -0,0 +1,102 @@ +{ + "id": "bgqegp3xcxnpfkb45olwigpkpi", + "title": "LabelCasing", + "version": 1, + "vault": { + "id": "stpebbaccrq72xulgouxsk4p7y", + "name": "Private" + }, + "category": "LOGIN", + "last_edited_by": "WOUTERRUYBH7BFPHMZ2KKGL6AU", + "created_at": "2023-09-12T08:30:07Z", + "updated_at": "2023-09-12T08:30:07Z", + "additional_information": "fluxility", + "sections": [ + { + "id": "7osqcvd43i75teocdzbb6d7mie", + "label": "section-with-values" + } + ], + "fields": [ + { + "id": "lowercaseid", + "type": "STRING", + "purpose": "USERNAME", + "label": "label0", + "value": "lowercaseid", + "reference": "op://Testcase/LabelCasing/lowercase" + }, + { + "id": "MiXeDcAsEiD", + "type": "STRING", + "purpose": "USERNAME", + "label": "label1", + "value": "mixedcaseid", + "reference": "op://Testcase/LabelCasing/lowercase" + }, + { + "id": "id1", + "type": "STRING", + "purpose": "USERNAME", + "label": "lowercaselabel", + "value": "lowercaselabel", + "reference": "op://Testcase/LabelCasing/lowercase" + }, + { + "id": "id2", + "type": "STRING", + "purpose": "USERNAME", + "label": "MiXeDcAsElAbEl", + "value": "mixedcaselabel", + "reference": "op://Testcase/LabelCasing/lowercase" + }, + { + "id": "sectionlowercaseid", + "type": "STRING", + "purpose": "USERNAME", + "label": "label2", + "value": "sectionlowercaseid", + "reference": "op://Testcase/LabelCasing/lowercase", + "section": { + "id": "7osqcvd43i75teocdzbb6d7mie", + "label": "section-with-values" + } + }, + { + "id": "SeCtIoNmIxEdCaSeId", + "type": "STRING", + "purpose": "USERNAME", + "label": "label3", + "value": "sectionmixedcaseid", + "reference": "op://Testcase/LabelCasing/lowercase", + "section": { + "id": "7osqcvd43i75teocdzbb6d7mie", + "label": "section-with-values" + } + }, + { + "id": "id3", + "type": "STRING", + "purpose": "USERNAME", + "label": "sectionlowercaselabel", + "value": "sectionlowercaselabel", + "reference": "op://Testcase/LabelCasing/lowercase", + "section": { + "id": "7osqcvd43i75teocdzbb6d7mie", + "label": "section-with-values" + } + }, + { + "id": "id2", + "type": "STRING", + "purpose": "USERNAME", + "label": "SeCtIoNmIxEdCaSeLaBeL", + "value": "sectionmixedcaselabel", + "reference": "op://Testcase/LabelCasing/lowercase", + "section": { + "id": "7osqcvd43i75teocdzbb6d7mie", + "label": "section-with-values" + } + } + ] +} diff --git a/ansible_collections/community/general/tests/unit/plugins/lookup/onepassword_fixtures/v2_out_05.json.license b/ansible_collections/community/general/tests/unit/plugins/lookup/onepassword_fixtures/v2_out_05.json.license new file mode 100644 index 000000000..969b956c2 --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/lookup/onepassword_fixtures/v2_out_05.json.license @@ -0,0 +1,3 @@ +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 +SPDX-FileCopyrightText: 2022, Ansible Project diff --git a/ansible_collections/community/general/tests/unit/plugins/lookup/test_bitwarden.py b/ansible_collections/community/general/tests/unit/plugins/lookup/test_bitwarden.py index d45263965..9270dd44e 100644 --- a/ansible_collections/community/general/tests/unit/plugins/lookup/test_bitwarden.py +++ b/ansible_collections/community/general/tests/unit/plugins/lookup/test_bitwarden.py @@ -14,10 +14,13 @@ from ansible.module_utils import six from ansible.plugins.loader import lookup_loader from ansible_collections.community.general.plugins.lookup.bitwarden import Bitwarden +MOCK_COLLECTION_ID = "3b12a9da-7c49-40b8-ad33-aede017a7ead" MOCK_RECORDS = [ { - "collectionIds": [], + "collectionIds": [ + MOCK_COLLECTION_ID + ], "deletedDate": None, "favorite": False, "fields": [ @@ -65,7 +68,9 @@ MOCK_RECORDS = [ "type": 1 }, { - "collectionIds": [], + "collectionIds": [ + MOCK_COLLECTION_ID + ], "deletedDate": None, "favorite": False, "folderId": None, @@ -85,7 +90,9 @@ MOCK_RECORDS = [ "type": 1 }, { - "collectionIds": [], + "collectionIds": [ + MOCK_COLLECTION_ID + ], "deletedDate": None, "favorite": False, "folderId": None, @@ -111,7 +118,10 @@ class MockBitwarden(Bitwarden): unlocked = True - def _get_matches(self, search_value, search_field="name", collection_id=None): + def _get_matches(self, search_value=None, search_field="name", collection_id=None): + if not search_value and collection_id: + return list(filter(lambda record: collection_id in record['collectionIds'], MOCK_RECORDS)) + return list(filter(lambda record: record[search_field] == search_value, MOCK_RECORDS)) @@ -156,5 +166,32 @@ class TestLookupModule(unittest.TestCase): def test_bitwarden_plugin_unlocked(self): record = MOCK_RECORDS[0] record_name = record['name'] - with self.assertRaises(AnsibleError): + with self.assertRaises(AnsibleError) as raised_error: self.lookup.run([record_name], field='password') + + self.assertEqual("Bitwarden Vault locked. Run 'bw unlock'.", str(raised_error.exception)) + + def test_bitwarden_plugin_without_session_option(self): + mock_bitwarden = MockBitwarden() + with patch("ansible_collections.community.general.plugins.lookup.bitwarden._bitwarden", mock_bitwarden): + record = MOCK_RECORDS[0] + record_name = record['name'] + session = 'session' + + self.lookup.run([record_name], field=None) + self.assertIsNone(mock_bitwarden.session) + + def test_bitwarden_plugin_session_option(self): + mock_bitwarden = MockBitwarden() + with patch("ansible_collections.community.general.plugins.lookup.bitwarden._bitwarden", mock_bitwarden): + record = MOCK_RECORDS[0] + record_name = record['name'] + session = 'session' + + self.lookup.run([record_name], field=None, bw_session=session) + self.assertEqual(mock_bitwarden.session, session) + + @patch('ansible_collections.community.general.plugins.lookup.bitwarden._bitwarden', new=MockBitwarden()) + def test_bitwarden_plugin_full_collection(self): + # Try to retrieve the full records of the given collection. + self.assertEqual(MOCK_RECORDS, self.lookup.run(None, collection_id=MOCK_COLLECTION_ID)[0]) diff --git a/ansible_collections/community/general/tests/unit/plugins/lookup/test_bitwarden_secrets_manager.py b/ansible_collections/community/general/tests/unit/plugins/lookup/test_bitwarden_secrets_manager.py new file mode 100644 index 000000000..aaeaf79ea --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/lookup/test_bitwarden_secrets_manager.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2023, jantari (https://github.com/jantari) +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +import json +from ansible_collections.community.general.tests.unit.compat import unittest +from ansible_collections.community.general.tests.unit.compat.mock import patch + +from ansible.errors import AnsibleLookupError +from ansible.plugins.loader import lookup_loader +from ansible_collections.community.general.plugins.lookup.bitwarden_secrets_manager import BitwardenSecretsManager + + +MOCK_SECRETS = [ + { + "object": "secret", + "id": "ababc4a8-c242-4e54-bceb-77d17cdf2e07", + "organizationId": "3c33066c-a0bf-4e70-9a3c-24cda6aaddd5", + "projectId": "81869439-bfe5-442f-8b4e-b172e68b0ab2", + "key": "TEST_SECRET", + "value": "1234supersecret5678", + "note": "A test secret to use when developing the ansible bitwarden_secrets_manager lookup plugin", + "creationDate": "2023-04-23T13:13:37.7507017Z", + "revisionDate": "2023-04-23T13:13:37.7507017Z" + }, + { + "object": "secret", + "id": "d4b7c8fa-fc95-40d7-a13c-6e186ee69d53", + "organizationId": "3c33066c-a0bf-4e70-9a3c-24cda6aaddd5", + "projectId": "81869439-bfe5-442f-8b4e-b172e68b0ab2", + "key": "TEST_SECRET_2", + "value": "abcd_such_secret_very_important_efgh", + "note": "notes go here", + "creationDate": "2023-04-23T13:26:44.0392906Z", + "revisionDate": "2023-04-23T13:26:44.0392906Z" + } +] + + +class MockBitwardenSecretsManager(BitwardenSecretsManager): + + def _run(self, args, stdin=None): + # secret_id is the last argument passed to the bws CLI + secret_id = args[-1] + rc = 1 + out = "" + err = "" + found_secrets = list(filter(lambda record: record["id"] == secret_id, MOCK_SECRETS)) + + if len(found_secrets) == 0: + err = "simulated bws CLI error: 404 no secret with such id" + elif len(found_secrets) == 1: + rc = 0 + # The real bws CLI will only ever return one secret / json object for the "get secret <secret-id>" command + out = json.dumps(found_secrets[0]) + else: + # This should never happen unless there's an error in the test MOCK_SECRETS. + # The real Bitwarden Secrets Manager assigns each secret a unique ID. + raise ValueError("More than 1 secret found with id: '{0}'. Impossible!".format(secret_id)) + + return out, err, rc + + +class TestLookupModule(unittest.TestCase): + + def setUp(self): + self.lookup = lookup_loader.get('community.general.bitwarden_secrets_manager') + + @patch('ansible_collections.community.general.plugins.lookup.bitwarden_secrets_manager._bitwarden_secrets_manager', new=MockBitwardenSecretsManager()) + def test_bitwarden_secrets_manager(self): + # Getting a secret by its id should return the full secret info + self.assertEqual([MOCK_SECRETS[0]], self.lookup.run(['ababc4a8-c242-4e54-bceb-77d17cdf2e07'], bws_access_token='123')) + + @patch('ansible_collections.community.general.plugins.lookup.bitwarden_secrets_manager._bitwarden_secrets_manager', new=MockBitwardenSecretsManager()) + def test_bitwarden_secrets_manager_no_match(self): + # Getting a nonexistent secret id throws exception + with self.assertRaises(AnsibleLookupError): + self.lookup.run(['nonexistant_id'], bws_access_token='123') diff --git a/ansible_collections/community/general/tests/unit/plugins/lookup/test_github_app_access_token.py b/ansible_collections/community/general/tests/unit/plugins/lookup/test_github_app_access_token.py new file mode 100644 index 000000000..4bf9c7e70 --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/lookup/test_github_app_access_token.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2023, Poh Wei Sheng <weisheng-p@hotmail.sg> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type +import json + +from ansible_collections.community.general.tests.unit.compat import unittest +from ansible_collections.community.general.tests.unit.compat.mock import ( + patch, + MagicMock, + mock_open +) +from ansible.plugins.loader import lookup_loader + + +class MockJWT(MagicMock): + def encode(self, payload, key, alg): + return 'Foobar' + + +class MockResponse(MagicMock): + response_token = 'Bar' + + def read(self): + return json.dumps({ + "token": self.response_token, + }).encode('utf-8') + + +class TestLookupModule(unittest.TestCase): + + def test_get_token(self): + with patch.multiple("ansible_collections.community.general.plugins.lookup.github_app_access_token", + open=mock_open(read_data="foo_bar"), + open_url=MagicMock(return_value=MockResponse()), + jwk_from_pem=MagicMock(return_value='private_key'), + jwt_instance=MockJWT(), + HAS_JWT=True): + lookup = lookup_loader.get('community.general.github_app_access_token') + self.assertListEqual( + [MockResponse.response_token], + lookup.run( + [], + key_path="key", + app_id="app_id", + installation_id="installation_id", + token_expiry=600 + ) + ) diff --git a/ansible_collections/community/general/tests/unit/plugins/lookup/test_merge_variables.py b/ansible_collections/community/general/tests/unit/plugins/lookup/test_merge_variables.py index 5085797b3..66cb2f08b 100644 --- a/ansible_collections/community/general/tests/unit/plugins/lookup/test_merge_variables.py +++ b/ansible_collections/community/general/tests/unit/plugins/lookup/test_merge_variables.py @@ -24,7 +24,7 @@ class TestMergeVariablesLookup(unittest.TestCase): self.merge_vars_lookup = merge_variables.LookupModule(loader=self.loader, templar=self.templar) @patch.object(AnsiblePlugin, 'set_options') - @patch.object(AnsiblePlugin, 'get_option', side_effect=[None, 'ignore', 'suffix']) + @patch.object(AnsiblePlugin, 'get_option', side_effect=[None, 'ignore', 'suffix', None]) @patch.object(Templar, 'template', side_effect=[['item1'], ['item3']]) def test_merge_list(self, mock_set_options, mock_get_option, mock_template): results = self.merge_vars_lookup.run(['__merge_list'], { @@ -36,7 +36,7 @@ class TestMergeVariablesLookup(unittest.TestCase): self.assertEqual(results, [['item1', 'item3']]) @patch.object(AnsiblePlugin, 'set_options') - @patch.object(AnsiblePlugin, 'get_option', side_effect=[['initial_item'], 'ignore', 'suffix']) + @patch.object(AnsiblePlugin, 'get_option', side_effect=[['initial_item'], 'ignore', 'suffix', None]) @patch.object(Templar, 'template', side_effect=[['item1'], ['item3']]) def test_merge_list_with_initial_value(self, mock_set_options, mock_get_option, mock_template): results = self.merge_vars_lookup.run(['__merge_list'], { @@ -48,7 +48,7 @@ class TestMergeVariablesLookup(unittest.TestCase): self.assertEqual(results, [['initial_item', 'item1', 'item3']]) @patch.object(AnsiblePlugin, 'set_options') - @patch.object(AnsiblePlugin, 'get_option', side_effect=[None, 'ignore', 'suffix']) + @patch.object(AnsiblePlugin, 'get_option', side_effect=[None, 'ignore', 'suffix', None]) @patch.object(Templar, 'template', side_effect=[{'item1': 'test', 'list_item': ['test1']}, {'item2': 'test', 'list_item': ['test2']}]) def test_merge_dict(self, mock_set_options, mock_get_option, mock_template): @@ -73,7 +73,7 @@ class TestMergeVariablesLookup(unittest.TestCase): @patch.object(AnsiblePlugin, 'set_options') @patch.object(AnsiblePlugin, 'get_option', side_effect=[{'initial_item': 'random value', 'list_item': ['test0']}, - 'ignore', 'suffix']) + 'ignore', 'suffix', None]) @patch.object(Templar, 'template', side_effect=[{'item1': 'test', 'list_item': ['test1']}, {'item2': 'test', 'list_item': ['test2']}]) def test_merge_dict_with_initial_value(self, mock_set_options, mock_get_option, mock_template): @@ -98,7 +98,7 @@ class TestMergeVariablesLookup(unittest.TestCase): ]) @patch.object(AnsiblePlugin, 'set_options') - @patch.object(AnsiblePlugin, 'get_option', side_effect=[None, 'warn', 'suffix']) + @patch.object(AnsiblePlugin, 'get_option', side_effect=[None, 'warn', 'suffix', None]) @patch.object(Templar, 'template', side_effect=[{'item': 'value1'}, {'item': 'value2'}]) @patch.object(Display, 'warning') def test_merge_dict_non_unique_warning(self, mock_set_options, mock_get_option, mock_template, mock_display): @@ -111,7 +111,7 @@ class TestMergeVariablesLookup(unittest.TestCase): self.assertEqual(results, [{'item': 'value2'}]) @patch.object(AnsiblePlugin, 'set_options') - @patch.object(AnsiblePlugin, 'get_option', side_effect=[None, 'error', 'suffix']) + @patch.object(AnsiblePlugin, 'get_option', side_effect=[None, 'error', 'suffix', None]) @patch.object(Templar, 'template', side_effect=[{'item': 'value1'}, {'item': 'value2'}]) def test_merge_dict_non_unique_error(self, mock_set_options, mock_get_option, mock_template): with self.assertRaises(AnsibleError): @@ -121,7 +121,7 @@ class TestMergeVariablesLookup(unittest.TestCase): }) @patch.object(AnsiblePlugin, 'set_options') - @patch.object(AnsiblePlugin, 'get_option', side_effect=[None, 'ignore', 'suffix']) + @patch.object(AnsiblePlugin, 'get_option', side_effect=[None, 'ignore', 'suffix', None]) @patch.object(Templar, 'template', side_effect=[{'item1': 'test', 'list_item': ['test1']}, ['item2', 'item3']]) def test_merge_list_and_dict(self, mock_set_options, mock_get_option, mock_template): @@ -133,3 +133,150 @@ class TestMergeVariablesLookup(unittest.TestCase): }, 'testdict__merge_var': ['item2', 'item3'] }) + + @patch.object(AnsiblePlugin, 'set_options') + @patch.object(AnsiblePlugin, 'get_option', side_effect=[None, 'ignore', 'suffix', ['all']]) + @patch.object(Templar, 'template', side_effect=[ + {'var': [{'item1': 'value1', 'item2': 'value2'}]}, + {'var': [{'item5': 'value5', 'item6': 'value6'}]}, + ]) + def test_merge_dict_group_all(self, mock_set_options, mock_get_option, mock_template): + results = self.merge_vars_lookup.run(['__merge_var'], { + 'inventory_hostname': 'host1', + 'hostvars': { + 'host1': { + 'group_names': ['dummy1'], + 'inventory_hostname': 'host1', + '1testlist__merge_var': { + 'var': [{'item1': 'value1', 'item2': 'value2'}] + } + }, + 'host2': { + 'group_names': ['dummy1'], + 'inventory_hostname': 'host2', + '2otherlist__merge_var': { + 'var': [{'item5': 'value5', 'item6': 'value6'}] + } + } + } + }) + + self.assertEqual(results, [ + {'var': [ + {'item1': 'value1', 'item2': 'value2'}, + {'item5': 'value5', 'item6': 'value6'} + ]} + ]) + + @patch.object(AnsiblePlugin, 'set_options') + @patch.object(AnsiblePlugin, 'get_option', side_effect=[None, 'ignore', 'suffix', ['dummy1']]) + @patch.object(Templar, 'template', side_effect=[ + {'var': [{'item1': 'value1', 'item2': 'value2'}]}, + {'var': [{'item5': 'value5', 'item6': 'value6'}]}, + ]) + def test_merge_dict_group_single(self, mock_set_options, mock_get_option, mock_template): + results = self.merge_vars_lookup.run(['__merge_var'], { + 'inventory_hostname': 'host1', + 'hostvars': { + 'host1': { + 'group_names': ['dummy1'], + 'inventory_hostname': 'host1', + '1testlist__merge_var': { + 'var': [{'item1': 'value1', 'item2': 'value2'}] + } + }, + 'host2': { + 'group_names': ['dummy1'], + 'inventory_hostname': 'host2', + '2otherlist__merge_var': { + 'var': [{'item5': 'value5', 'item6': 'value6'}] + } + }, + 'host3': { + 'group_names': ['dummy2'], + 'inventory_hostname': 'host3', + '3otherlist__merge_var': { + 'var': [{'item3': 'value3', 'item4': 'value4'}] + } + } + } + }) + + self.assertEqual(results, [ + {'var': [ + {'item1': 'value1', 'item2': 'value2'}, + {'item5': 'value5', 'item6': 'value6'} + ]} + ]) + + @patch.object(AnsiblePlugin, 'set_options') + @patch.object(AnsiblePlugin, 'get_option', side_effect=[None, 'ignore', 'suffix', ['dummy1', 'dummy2']]) + @patch.object(Templar, 'template', side_effect=[ + {'var': [{'item1': 'value1', 'item2': 'value2'}]}, + {'var': [{'item5': 'value5', 'item6': 'value6'}]}, + ]) + def test_merge_dict_group_multiple(self, mock_set_options, mock_get_option, mock_template): + results = self.merge_vars_lookup.run(['__merge_var'], { + 'inventory_hostname': 'host1', + 'hostvars': { + 'host1': { + 'group_names': ['dummy1'], + 'inventory_hostname': 'host1', + '1testlist__merge_var': { + 'var': [{'item1': 'value1', 'item2': 'value2'}] + } + }, + 'host2': { + 'group_names': ['dummy2'], + 'inventory_hostname': 'host2', + '2otherlist__merge_var': { + 'var': [{'item5': 'value5', 'item6': 'value6'}] + } + }, + 'host3': { + 'group_names': ['dummy3'], + 'inventory_hostname': 'host3', + '3otherlist__merge_var': { + 'var': [{'item3': 'value3', 'item4': 'value4'}] + } + } + } + }) + + self.assertEqual(results, [ + {'var': [ + {'item1': 'value1', 'item2': 'value2'}, + {'item5': 'value5', 'item6': 'value6'} + ]} + ]) + + @patch.object(AnsiblePlugin, 'set_options') + @patch.object(AnsiblePlugin, 'get_option', side_effect=[None, 'ignore', 'suffix', ['dummy1', 'dummy2']]) + @patch.object(Templar, 'template', side_effect=[ + ['item1'], + ['item5'], + ]) + def test_merge_list_group_multiple(self, mock_set_options, mock_get_option, mock_template): + print() + results = self.merge_vars_lookup.run(['__merge_var'], { + 'inventory_hostname': 'host1', + 'hostvars': { + 'host1': { + 'group_names': ['dummy1'], + 'inventory_hostname': 'host1', + '1testlist__merge_var': ['item1'] + }, + 'host2': { + 'group_names': ['dummy2'], + 'inventory_hostname': 'host2', + '2otherlist__merge_var': ['item5'] + }, + 'host3': { + 'group_names': ['dummy3'], + 'inventory_hostname': 'host3', + '3otherlist__merge_var': ['item3'] + } + } + }) + + self.assertEqual(results, [['item1', 'item5']]) diff --git a/ansible_collections/community/general/tests/unit/plugins/lookup/test_onepassword.py b/ansible_collections/community/general/tests/unit/plugins/lookup/test_onepassword.py index ab7f3def2..dc00e5703 100644 --- a/ansible_collections/community/general/tests/unit/plugins/lookup/test_onepassword.py +++ b/ansible_collections/community/general/tests/unit/plugins/lookup/test_onepassword.py @@ -10,15 +10,9 @@ import itertools import json import pytest -from .onepassword_conftest import ( # noqa: F401, pylint: disable=unused-import - OP_VERSION_FIXTURES, - fake_op, - opv1, - opv2, -) from .onepassword_common import MOCK_ENTRIES -from ansible.errors import AnsibleLookupError +from ansible.errors import AnsibleLookupError, AnsibleOptionsError from ansible.plugins.loader import lookup_loader from ansible_collections.community.general.plugins.lookup.onepassword import ( OnePassCLIv1, @@ -26,6 +20,12 @@ from ansible_collections.community.general.plugins.lookup.onepassword import ( ) +OP_VERSION_FIXTURES = [ + "opv1", + "opv2" +] + + @pytest.mark.parametrize( ("args", "rc", "expected_call_args", "expected_call_kwargs", "expected"), ( @@ -82,6 +82,12 @@ def test_assert_logged_in_v2(mocker, args, out, expected_call_args, expected_cal assert result == expected +def test_assert_logged_in_v2_connect(): + op_cli = OnePassCLIv2(connect_host="http://localhost:8080", connect_token="foobar") + result = op_cli.assert_logged_in() + assert result + + def test_full_signin_v2(mocker): mocker.patch.object(OnePassCLIv2, "_run", return_value=[0, "", ""]) @@ -264,5 +270,50 @@ def test_signin(op_fixture, request): op = request.getfixturevalue(op_fixture) op._cli.master_password = "master_pass" op._cli.signin() - print(op._cli.version) op._cli._run.assert_called_once_with(['signin', '--raw'], command_input=b"master_pass") + + +def test_op_doc(mocker): + document_contents = "Document Contents\n" + + mocker.patch("ansible_collections.community.general.plugins.lookup.onepassword.OnePass.assert_logged_in", return_value=True) + mocker.patch("ansible_collections.community.general.plugins.lookup.onepassword.OnePassCLIBase._run", return_value=(0, document_contents, "")) + + op_lookup = lookup_loader.get("community.general.onepassword_doc") + result = op_lookup.run(["Private key doc"]) + + assert result == [document_contents] + + +@pytest.mark.parametrize( + ("plugin", "connect_host", "connect_token"), + [ + (plugin, connect_host, connect_token) + for plugin in ("community.general.onepassword", "community.general.onepassword_raw") + for (connect_host, connect_token) in + ( + ("http://localhost", None), + (None, "foobar"), + ) + ] +) +def test_op_connect_partial_args(plugin, connect_host, connect_token, mocker): + op_lookup = lookup_loader.get(plugin) + + mocker.patch("ansible_collections.community.general.plugins.lookup.onepassword.OnePass._get_cli_class", OnePassCLIv2) + + with pytest.raises(AnsibleOptionsError): + op_lookup.run("login", vault_name="test vault", connect_host=connect_host, connect_token=connect_token) + + +@pytest.mark.parametrize( + ("kwargs"), + ( + {"connect_host": "http://localhost", "connect_token": "foobar"}, + {"service_account_token": "foobar"}, + ) +) +def test_opv1_unsupported_features(kwargs): + op_cli = OnePassCLIv1(**kwargs) + with pytest.raises(AnsibleLookupError): + op_cli.full_signin() diff --git a/ansible_collections/community/general/tests/unit/plugins/module_utils/hwc/test_hwc_utils.py b/ansible_collections/community/general/tests/unit/plugins/module_utils/hwc/test_hwc_utils.py index 1344496b1..9b0be0bb4 100644 --- a/ansible_collections/community/general/tests/unit/plugins/module_utils/hwc/test_hwc_utils.py +++ b/ansible_collections/community/general/tests/unit/plugins/module_utils/hwc/test_hwc_utils.py @@ -6,11 +6,20 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type +import sys + from ansible_collections.community.general.tests.unit.compat import unittest from ansible_collections.community.general.plugins.module_utils.hwc_utils import (HwcModuleException, navigate_value) class HwcUtilsTestCase(unittest.TestCase): + def setUp(self): + super(HwcUtilsTestCase, self).setUp() + + # Add backward compatibility + if sys.version_info < (3, 0): + self.assertRaisesRegex = self.assertRaisesRegexp + def test_navigate_value(self): value = { 'foo': { @@ -29,12 +38,12 @@ class HwcUtilsTestCase(unittest.TestCase): {"foo.quiet.trees": 1}), 1) - self.assertRaisesRegexp(HwcModuleException, - r".* key\(q\) is not exist in dict", - navigate_value, value, ["foo", "q", "tree"]) + self.assertRaisesRegex(HwcModuleException, + r".* key\(q\) is not exist in dict", + navigate_value, value, ["foo", "q", "tree"]) - self.assertRaisesRegexp(HwcModuleException, - r".* the index is out of list", - navigate_value, value, - ["foo", "quiet", "trees"], - {"foo.quiet.trees": 2}) + self.assertRaisesRegex(HwcModuleException, + r".* the index is out of list", + navigate_value, value, + ["foo", "quiet", "trees"], + {"foo.quiet.trees": 2}) diff --git a/ansible_collections/community/general/tests/unit/plugins/module_utils/test_cmd_runner.py b/ansible_collections/community/general/tests/unit/plugins/module_utils/test_cmd_runner.py index 7cec215a7..86576e8ce 100644 --- a/ansible_collections/community/general/tests/unit/plugins/module_utils/test_cmd_runner.py +++ b/ansible_collections/community/general/tests/unit/plugins/module_utils/test_cmd_runner.py @@ -11,44 +11,44 @@ from sys import version_info import pytest from ansible_collections.community.general.tests.unit.compat.mock import MagicMock, PropertyMock -from ansible_collections.community.general.plugins.module_utils.cmd_runner import CmdRunner, fmt +from ansible_collections.community.general.plugins.module_utils.cmd_runner import CmdRunner, cmd_runner_fmt TC_FORMATS = dict( - simple_boolean__true=(fmt.as_bool, ("--superflag",), True, ["--superflag"]), - simple_boolean__false=(fmt.as_bool, ("--superflag",), False, []), - simple_boolean__none=(fmt.as_bool, ("--superflag",), None, []), - simple_boolean_both__true=(fmt.as_bool, ("--superflag", "--falseflag"), True, ["--superflag"]), - simple_boolean_both__false=(fmt.as_bool, ("--superflag", "--falseflag"), False, ["--falseflag"]), - simple_boolean_both__none=(fmt.as_bool, ("--superflag", "--falseflag"), None, ["--falseflag"]), - simple_boolean_both__none_ig=(fmt.as_bool, ("--superflag", "--falseflag", True), None, []), - simple_boolean_not__true=(fmt.as_bool_not, ("--superflag",), True, []), - simple_boolean_not__false=(fmt.as_bool_not, ("--superflag",), False, ["--superflag"]), - simple_boolean_not__none=(fmt.as_bool_not, ("--superflag",), None, ["--superflag"]), - simple_optval__str=(fmt.as_optval, ("-t",), "potatoes", ["-tpotatoes"]), - simple_optval__int=(fmt.as_optval, ("-t",), 42, ["-t42"]), - simple_opt_val__str=(fmt.as_opt_val, ("-t",), "potatoes", ["-t", "potatoes"]), - simple_opt_val__int=(fmt.as_opt_val, ("-t",), 42, ["-t", "42"]), - simple_opt_eq_val__str=(fmt.as_opt_eq_val, ("--food",), "potatoes", ["--food=potatoes"]), - simple_opt_eq_val__int=(fmt.as_opt_eq_val, ("--answer",), 42, ["--answer=42"]), - simple_list_potato=(fmt.as_list, (), "literal_potato", ["literal_potato"]), - simple_list_42=(fmt.as_list, (), 42, ["42"]), - simple_map=(fmt.as_map, ({'a': 1, 'b': 2, 'c': 3},), 'b', ["2"]), - simple_default_type__list=(fmt.as_default_type, ("list",), [1, 2, 3, 5, 8], ["--1", "--2", "--3", "--5", "--8"]), - simple_default_type__bool_true=(fmt.as_default_type, ("bool", "what"), True, ["--what"]), - simple_default_type__bool_false=(fmt.as_default_type, ("bool", "what"), False, []), - simple_default_type__potato=(fmt.as_default_type, ("any-other-type", "potato"), "42", ["--potato", "42"]), - simple_fixed_true=(fmt.as_fixed, [("--always-here", "--forever")], True, ["--always-here", "--forever"]), - simple_fixed_false=(fmt.as_fixed, [("--always-here", "--forever")], False, ["--always-here", "--forever"]), - simple_fixed_none=(fmt.as_fixed, [("--always-here", "--forever")], None, ["--always-here", "--forever"]), - simple_fixed_str=(fmt.as_fixed, [("--always-here", "--forever")], "something", ["--always-here", "--forever"]), + simple_boolean__true=(cmd_runner_fmt.as_bool, ("--superflag",), True, ["--superflag"]), + simple_boolean__false=(cmd_runner_fmt.as_bool, ("--superflag",), False, []), + simple_boolean__none=(cmd_runner_fmt.as_bool, ("--superflag",), None, []), + simple_boolean_both__true=(cmd_runner_fmt.as_bool, ("--superflag", "--falseflag"), True, ["--superflag"]), + simple_boolean_both__false=(cmd_runner_fmt.as_bool, ("--superflag", "--falseflag"), False, ["--falseflag"]), + simple_boolean_both__none=(cmd_runner_fmt.as_bool, ("--superflag", "--falseflag"), None, ["--falseflag"]), + simple_boolean_both__none_ig=(cmd_runner_fmt.as_bool, ("--superflag", "--falseflag", True), None, []), + simple_boolean_not__true=(cmd_runner_fmt.as_bool_not, ("--superflag",), True, []), + simple_boolean_not__false=(cmd_runner_fmt.as_bool_not, ("--superflag",), False, ["--superflag"]), + simple_boolean_not__none=(cmd_runner_fmt.as_bool_not, ("--superflag",), None, ["--superflag"]), + simple_optval__str=(cmd_runner_fmt.as_optval, ("-t",), "potatoes", ["-tpotatoes"]), + simple_optval__int=(cmd_runner_fmt.as_optval, ("-t",), 42, ["-t42"]), + simple_opt_val__str=(cmd_runner_fmt.as_opt_val, ("-t",), "potatoes", ["-t", "potatoes"]), + simple_opt_val__int=(cmd_runner_fmt.as_opt_val, ("-t",), 42, ["-t", "42"]), + simple_opt_eq_val__str=(cmd_runner_fmt.as_opt_eq_val, ("--food",), "potatoes", ["--food=potatoes"]), + simple_opt_eq_val__int=(cmd_runner_fmt.as_opt_eq_val, ("--answer",), 42, ["--answer=42"]), + simple_list_potato=(cmd_runner_fmt.as_list, (), "literal_potato", ["literal_potato"]), + simple_list_42=(cmd_runner_fmt.as_list, (), 42, ["42"]), + simple_map=(cmd_runner_fmt.as_map, ({'a': 1, 'b': 2, 'c': 3},), 'b', ["2"]), + simple_default_type__list=(cmd_runner_fmt.as_default_type, ("list",), [1, 2, 3, 5, 8], ["--1", "--2", "--3", "--5", "--8"]), + simple_default_type__bool_true=(cmd_runner_fmt.as_default_type, ("bool", "what"), True, ["--what"]), + simple_default_type__bool_false=(cmd_runner_fmt.as_default_type, ("bool", "what"), False, []), + simple_default_type__potato=(cmd_runner_fmt.as_default_type, ("any-other-type", "potato"), "42", ["--potato", "42"]), + simple_fixed_true=(cmd_runner_fmt.as_fixed, [("--always-here", "--forever")], True, ["--always-here", "--forever"]), + simple_fixed_false=(cmd_runner_fmt.as_fixed, [("--always-here", "--forever")], False, ["--always-here", "--forever"]), + simple_fixed_none=(cmd_runner_fmt.as_fixed, [("--always-here", "--forever")], None, ["--always-here", "--forever"]), + simple_fixed_str=(cmd_runner_fmt.as_fixed, [("--always-here", "--forever")], "something", ["--always-here", "--forever"]), ) if tuple(version_info) >= (3, 1): from collections import OrderedDict # needs OrderedDict to provide a consistent key order TC_FORMATS["simple_default_type__dict"] = ( # type: ignore - fmt.as_default_type, + cmd_runner_fmt.as_default_type, ("dict",), OrderedDict((('a', 1), ('b', 2))), ["--a=1", "--b=2"] @@ -76,11 +76,11 @@ TC_RUNNER = dict( # param1=dict( # type="int", # value=11, - # fmt_func=fmt.as_opt_eq_val, + # fmt_func=cmd_runner_fmt.as_opt_eq_val, # fmt_arg="--answer", # ), # param2=dict( - # fmt_func=fmt.as_bool, + # fmt_func=cmd_runner_fmt.as_bool, # fmt_arg="--bb-here", # ) # ), @@ -119,8 +119,8 @@ TC_RUNNER = dict( aa_bb=( dict( args_bundle=dict( - aa=dict(type="int", value=11, fmt_func=fmt.as_opt_eq_val, fmt_arg="--answer"), - bb=dict(fmt_func=fmt.as_bool, fmt_arg="--bb-here"), + aa=dict(type="int", value=11, fmt_func=cmd_runner_fmt.as_opt_eq_val, fmt_arg="--answer"), + bb=dict(fmt_func=cmd_runner_fmt.as_bool, fmt_arg="--bb-here"), ), runner_init_args=dict(), runner_ctx_args=dict(args_order=['aa', 'bb']), @@ -137,8 +137,8 @@ TC_RUNNER = dict( aa_bb_default_order=( dict( args_bundle=dict( - aa=dict(type="int", value=11, fmt_func=fmt.as_opt_eq_val, fmt_arg="--answer"), - bb=dict(fmt_func=fmt.as_bool, fmt_arg="--bb-here"), + aa=dict(type="int", value=11, fmt_func=cmd_runner_fmt.as_opt_eq_val, fmt_arg="--answer"), + bb=dict(fmt_func=cmd_runner_fmt.as_bool, fmt_arg="--bb-here"), ), runner_init_args=dict(default_args_order=['bb', 'aa']), runner_ctx_args=dict(), @@ -155,8 +155,8 @@ TC_RUNNER = dict( aa_bb_default_order_args_order=( dict( args_bundle=dict( - aa=dict(type="int", value=11, fmt_func=fmt.as_opt_eq_val, fmt_arg="--answer"), - bb=dict(fmt_func=fmt.as_bool, fmt_arg="--bb-here"), + aa=dict(type="int", value=11, fmt_func=cmd_runner_fmt.as_opt_eq_val, fmt_arg="--answer"), + bb=dict(fmt_func=cmd_runner_fmt.as_bool, fmt_arg="--bb-here"), ), runner_init_args=dict(default_args_order=['bb', 'aa']), runner_ctx_args=dict(args_order=['aa', 'bb']), @@ -173,8 +173,8 @@ TC_RUNNER = dict( aa_bb_dup_in_args_order=( dict( args_bundle=dict( - aa=dict(type="int", value=11, fmt_func=fmt.as_opt_eq_val, fmt_arg="--answer"), - bb=dict(fmt_func=fmt.as_bool, fmt_arg="--bb-here"), + aa=dict(type="int", value=11, fmt_func=cmd_runner_fmt.as_opt_eq_val, fmt_arg="--answer"), + bb=dict(fmt_func=cmd_runner_fmt.as_bool, fmt_arg="--bb-here"), ), runner_init_args=dict(), runner_ctx_args=dict(args_order=['aa', 'bb', 'aa']), @@ -189,8 +189,8 @@ TC_RUNNER = dict( aa_bb_process_output=( dict( args_bundle=dict( - aa=dict(type="int", value=11, fmt_func=fmt.as_opt_eq_val, fmt_arg="--answer"), - bb=dict(fmt_func=fmt.as_bool, fmt_arg="--bb-here"), + aa=dict(type="int", value=11, fmt_func=cmd_runner_fmt.as_opt_eq_val, fmt_arg="--answer"), + bb=dict(fmt_func=cmd_runner_fmt.as_bool, fmt_arg="--bb-here"), ), runner_init_args=dict(default_args_order=['bb', 'aa']), runner_ctx_args=dict( @@ -209,8 +209,8 @@ TC_RUNNER = dict( aa_bb_ignore_none_with_none=( dict( args_bundle=dict( - aa=dict(type="int", value=49, fmt_func=fmt.as_opt_eq_val, fmt_arg="--answer"), - bb=dict(fmt_func=fmt.as_bool, fmt_arg="--bb-here"), + aa=dict(type="int", value=49, fmt_func=cmd_runner_fmt.as_opt_eq_val, fmt_arg="--answer"), + bb=dict(fmt_func=cmd_runner_fmt.as_bool, fmt_arg="--bb-here"), ), runner_init_args=dict(default_args_order=['bb', 'aa']), runner_ctx_args=dict( @@ -228,8 +228,8 @@ TC_RUNNER = dict( aa_bb_ignore_not_none_with_none=( dict( args_bundle=dict( - aa=dict(type="int", value=49, fmt_func=fmt.as_opt_eq_val, fmt_arg="--answer"), - bb=dict(fmt_func=fmt.as_bool, fmt_arg="--bb-here"), + aa=dict(type="int", value=49, fmt_func=cmd_runner_fmt.as_opt_eq_val, fmt_arg="--answer"), + bb=dict(fmt_func=cmd_runner_fmt.as_bool, fmt_arg="--bb-here"), ), runner_init_args=dict(default_args_order=['bb', 'aa']), runner_ctx_args=dict( @@ -247,8 +247,8 @@ TC_RUNNER = dict( aa_bb_fixed=( dict( args_bundle=dict( - aa=dict(type="int", value=11, fmt_func=fmt.as_opt_eq_val, fmt_arg="--answer"), - bb=dict(fmt_func=fmt.as_fixed, fmt_arg=["fixed", "args"]), + aa=dict(type="int", value=11, fmt_func=cmd_runner_fmt.as_opt_eq_val, fmt_arg="--answer"), + bb=dict(fmt_func=cmd_runner_fmt.as_fixed, fmt_arg=["fixed", "args"]), ), runner_init_args=dict(), runner_ctx_args=dict(args_order=['aa', 'bb']), @@ -265,8 +265,8 @@ TC_RUNNER = dict( aa_bb_map=( dict( args_bundle=dict( - aa=dict(type="int", value=11, fmt_func=fmt.as_opt_eq_val, fmt_arg="--answer"), - bb=dict(fmt_func=fmt.as_map, fmt_arg={"v1": 111, "v2": 222}), + aa=dict(type="int", value=11, fmt_func=cmd_runner_fmt.as_opt_eq_val, fmt_arg="--answer"), + bb=dict(fmt_func=cmd_runner_fmt.as_map, fmt_arg={"v1": 111, "v2": 222}), ), runner_init_args=dict(), runner_ctx_args=dict(args_order=['aa', 'bb']), @@ -283,8 +283,8 @@ TC_RUNNER = dict( aa_bb_map_default=( dict( args_bundle=dict( - aa=dict(type="int", value=11, fmt_func=fmt.as_opt_eq_val, fmt_arg="--answer"), - bb=dict(fmt_func=fmt.as_map, fmt_arg={"v1": 111, "v2": 222}), + aa=dict(type="int", value=11, fmt_func=cmd_runner_fmt.as_opt_eq_val, fmt_arg="--answer"), + bb=dict(fmt_func=cmd_runner_fmt.as_map, fmt_arg={"v1": 111, "v2": 222}), ), runner_init_args=dict(), runner_ctx_args=dict(args_order=['aa', 'bb']), diff --git a/ansible_collections/community/general/tests/unit/plugins/module_utils/test_module_helper.py b/ansible_collections/community/general/tests/unit/plugins/module_utils/test_module_helper.py index 3d8a4b654..b2cd58690 100644 --- a/ansible_collections/community/general/tests/unit/plugins/module_utils/test_module_helper.py +++ b/ansible_collections/community/general/tests/unit/plugins/module_utils/test_module_helper.py @@ -10,88 +10,10 @@ __metaclass__ = type import pytest from ansible_collections.community.general.plugins.module_utils.module_helper import ( - ArgFormat, DependencyCtxMgr, VarMeta, VarDict, cause_changes + DependencyCtxMgr, VarMeta, VarDict, cause_changes ) -def single_lambda_2star(x, y, z): - return ["piggies=[{0},{1},{2}]".format(x, y, z)] - - -ARG_FORMATS = dict( - simple_boolean_true=("--superflag", ArgFormat.BOOLEAN, 0, - True, ["--superflag"]), - simple_boolean_false=("--superflag", ArgFormat.BOOLEAN, 0, - False, []), - simple_boolean_none=("--superflag", ArgFormat.BOOLEAN, 0, - None, []), - simple_boolean_not_true=("--superflag", ArgFormat.BOOLEAN_NOT, 0, - True, []), - simple_boolean_not_false=("--superflag", ArgFormat.BOOLEAN_NOT, 0, - False, ["--superflag"]), - simple_boolean_not_none=("--superflag", ArgFormat.BOOLEAN_NOT, 0, - None, ["--superflag"]), - single_printf=("--param=%s", ArgFormat.PRINTF, 0, - "potatoes", ["--param=potatoes"]), - single_printf_no_substitution=("--param", ArgFormat.PRINTF, 0, - "potatoes", ["--param"]), - single_printf_none=("--param=%s", ArgFormat.PRINTF, 0, - None, []), - multiple_printf=(["--param", "free-%s"], ArgFormat.PRINTF, 0, - "potatoes", ["--param", "free-potatoes"]), - single_format=("--param={0}", ArgFormat.FORMAT, 0, - "potatoes", ["--param=potatoes"]), - single_format_none=("--param={0}", ArgFormat.FORMAT, 0, - None, []), - single_format_no_substitution=("--param", ArgFormat.FORMAT, 0, - "potatoes", ["--param"]), - multiple_format=(["--param", "free-{0}"], ArgFormat.FORMAT, 0, - "potatoes", ["--param", "free-potatoes"]), - multiple_format_none=(["--param", "free-{0}"], ArgFormat.FORMAT, 0, - None, []), - single_lambda_0star=((lambda v: ["piggies=[{0},{1},{2}]".format(v[0], v[1], v[2])]), None, 0, - ['a', 'b', 'c'], ["piggies=[a,b,c]"]), - single_lambda_0star_none=((lambda v: ["piggies=[{0},{1},{2}]".format(v[0], v[1], v[2])]), None, 0, - None, []), - single_lambda_1star=((lambda a, b, c: ["piggies=[{0},{1},{2}]".format(a, b, c)]), None, 1, - ['a', 'b', 'c'], ["piggies=[a,b,c]"]), - single_lambda_1star_none=((lambda a, b, c: ["piggies=[{0},{1},{2}]".format(a, b, c)]), None, 1, - None, []), - single_lambda_2star=(single_lambda_2star, None, 2, - dict(z='c', x='a', y='b'), ["piggies=[a,b,c]"]), - single_lambda_2star_none=(single_lambda_2star, None, 2, - None, []), -) -ARG_FORMATS_IDS = sorted(ARG_FORMATS.keys()) - - -@pytest.mark.parametrize('fmt, style, stars, value, expected', - (ARG_FORMATS[tc] for tc in ARG_FORMATS_IDS), - ids=ARG_FORMATS_IDS) -def test_arg_format(fmt, style, stars, value, expected): - af = ArgFormat('name', fmt, style, stars) - actual = af.to_text(value) - print("formatted string = {0}".format(actual)) - assert actual == expected, "actual = {0}".format(actual) - - -ARG_FORMATS_FAIL = dict( - int_fmt=(3, None, 0, "", [""]), - bool_fmt=(True, None, 0, "", [""]), -) -ARG_FORMATS_FAIL_IDS = sorted(ARG_FORMATS_FAIL.keys()) - - -@pytest.mark.parametrize('fmt, style, stars, value, expected', - (ARG_FORMATS_FAIL[tc] for tc in ARG_FORMATS_FAIL_IDS), - ids=ARG_FORMATS_FAIL_IDS) -def test_arg_format_fail(fmt, style, stars, value, expected): - with pytest.raises(TypeError): - af = ArgFormat('name', fmt, style, stars) - actual = af.to_text(value) - print("formatted string = {0}".format(actual)) - - def test_dependency_ctxmgr(): ctx = DependencyCtxMgr("POTATOES", "Potatoes must be installed") with ctx: diff --git a/ansible_collections/community/general/tests/unit/plugins/module_utils/test_vardict.py b/ansible_collections/community/general/tests/unit/plugins/module_utils/test_vardict.py new file mode 100644 index 000000000..01d710b44 --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/module_utils/test_vardict.py @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- +# (c) 2023, Alexei Znamensky <russoz@gmail.com> +# Copyright (c) 2023 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.plugins.module_utils.vardict import VarDict + + +def test_var_simple(): + vd = VarDict() + vd["a"] = 123 + + var = vd._var("a") + assert var.output is True + assert var.diff is False + assert var.change is False + assert var.fact is False + assert var.initial_value == 123 + assert var.value == 123 + + vd.a = 456 + assert var.output is True + assert var.diff is False + assert var.change is False + assert var.fact is False + assert var.initial_value == 123 + assert var.value == 456 + + +def test_var_diff_scalar(): + vd = VarDict() + vd.set("aa", 123, diff=True) + + var = vd._var("aa") + assert var.output is True + assert var.diff is True + assert var.change is True + assert var.fact is False + assert var.initial_value == 123 + assert var.value == 123 + assert vd.diff() is None + + vd.aa = 456 + assert var.output is True + assert var.diff is True + assert var.change is True + assert var.fact is False + assert var.initial_value == 123 + assert var.value == 456 + assert vd.diff() == {"before": {"aa": 123}, "after": {"aa": 456}}, "actual={0}".format(vd.diff()) + + +def test_var_diff_dict(): + val_before = dict(x=0, y=10, z=10) + val_after = dict(x=0, y=30, z=10) + + vd = VarDict() + vd.set("dd", val_before, diff=True) + + var = vd._var("dd") + assert var.output is True + assert var.diff is True + assert var.change is True + assert var.fact is False + assert var.initial_value == val_before + assert var.value == val_before + assert vd.diff() is None + + vd.dd = val_after + assert var.output is True + assert var.diff is True + assert var.change is True + assert var.fact is False + assert var.initial_value == val_before + assert var.value == val_after + assert vd.diff() == {"before": {"dd": val_before}, "after": {"dd": val_after}}, "actual={0}".format(vd.diff()) + + vd.set("aa", 123, diff=True) + vd.aa = 456 + assert vd.diff() == {"before": {"aa": 123, "dd": val_before}, "after": {"aa": 456, "dd": val_after}}, "actual={0}".format(vd.diff()) + + +def test_vardict_set_meta(): + vd = VarDict() + vd["jj"] = 123 + + var = vd._var("jj") + assert var.output is True + assert var.diff is False + assert var.change is False + assert var.fact is False + assert var.initial_value == 123 + assert var.value == 123 + + vd.set_meta("jj", diff=True) + assert var.diff is True + assert var.change is True + + vd.set_meta("jj", diff=False) + assert var.diff is False + assert var.change is False + + vd.set_meta("jj", change=False) + vd.set_meta("jj", diff=True) + assert var.diff is True + assert var.change is False + + +def test_vardict_change(): + vd = VarDict() + vd.set("xx", 123, change=True) + vd.set("yy", 456, change=True) + vd.set("zz", 789, change=True) + + vd.xx = 123 + vd.yy = 456 + assert vd.has_changed is False + vd.xx = 12345 + assert vd.has_changed is True + + +def test_vardict_dict(): + vd = VarDict() + vd.set("xx", 123) + vd.set("yy", 456) + vd.set("zz", 789) + + assert vd.as_dict() == {"xx": 123, "yy": 456, "zz": 789} + assert vd.get_meta("xx") == {"output": True, "change": False, "diff": False, "fact": False, "verbosity": 0} diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/gitlab.py b/ansible_collections/community/general/tests/unit/plugins/modules/gitlab.py index c64d99fff..7a52dc355 100644 --- a/ansible_collections/community/general/tests/unit/plugins/modules/gitlab.py +++ b/ansible_collections/community/general/tests/unit/plugins/modules/gitlab.py @@ -284,6 +284,36 @@ def resp_delete_group(url, request): return response(204, content, headers, None, 5, request) +@urlmatch(scheme="http", netloc="localhost", path="/api/v4/groups/1/access_tokens", method="get") +def resp_list_group_access_tokens(url, request): + headers = {'content-type': 'application/json'} + content = ('[{"user_id" : 1, "scopes" : ["api"], "name" : "token1", "expires_at" : "2021-01-31",' + '"id" : 1, "active" : false, "created_at" : "2021-01-20T22:11:48.151Z", "revoked" : true,' + '"access_level": 40},{"user_id" : 2, "scopes" : ["api"], "name" : "token2", "expires_at" : "2021-02-31",' + '"id" : 2, "active" : true, "created_at" : "2021-02-20T22:11:48.151Z", "revoked" : false,' + '"access_level": 40}]') + content = content.encode("utf-8") + return response(200, content, headers, None, 5, request) + + +@urlmatch(scheme="http", netloc="localhost", path="/api/v4/groups/1/access_tokens", method="post") +def resp_create_group_access_tokens(url, request): + headers = {'content-type': 'application/json'} + content = ('{"user_id" : 1, "scopes" : ["api"], "name" : "token1", "expires_at" : "2021-01-31",' + '"id" : 1, "active" : false, "created_at" : "2021-01-20T22:11:48.151Z", "revoked" : true,' + '"access_level": 40, "token": "Der423FErcdv35qEEWc"}') + content = content.encode("utf-8") + return response(201, content, headers, None, 5, request) + + +@urlmatch(scheme="http", netloc="localhost", path="/api/v4/groups/1/access_tokens/1", method="delete") +def resp_revoke_group_access_tokens(url, request): + headers = {'content-type': 'application/json'} + content = ('') + content = content.encode("utf-8") + return response(204, content, headers, None, 5, request) + + ''' GROUP MEMBER API ''' @@ -534,6 +564,36 @@ def resp_delete_protected_branch(url, request): return response(204, content, headers, None, 5, request) +@urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects/1/access_tokens", method="get") +def resp_list_project_access_tokens(url, request): + headers = {'content-type': 'application/json'} + content = ('[{"user_id" : 1, "scopes" : ["api"], "name" : "token1", "expires_at" : "2021-01-31",' + '"id" : 1, "active" : false, "created_at" : "2021-01-20T22:11:48.151Z", "revoked" : true,' + '"access_level": 40},{"user_id" : 2, "scopes" : ["api"], "name" : "token2", "expires_at" : "2021-02-31",' + '"id" : 2, "active" : true, "created_at" : "2021-02-20T22:11:48.151Z", "revoked" : false,' + '"access_level": 40}]') + content = content.encode("utf-8") + return response(200, content, headers, None, 5, request) + + +@urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects/1/access_tokens", method="post") +def resp_create_project_access_tokens(url, request): + headers = {'content-type': 'application/json'} + content = ('{"user_id" : 1, "scopes" : ["api"], "name" : "token1", "expires_at" : "2021-01-31",' + '"id" : 1, "active" : false, "created_at" : "2021-01-20T22:11:48.151Z", "revoked" : true,' + '"access_level": 40, "token": "Der423FErcdv35qEEWc"}') + content = content.encode("utf-8") + return response(201, content, headers, None, 5, request) + + +@urlmatch(scheme="http", netloc="localhost", path="/api/v4/projects/1/access_tokens/1", method="delete") +def resp_revoke_project_access_tokens(url, request): + headers = {'content-type': 'application/json'} + content = ('') + content = content.encode("utf-8") + return response(204, content, headers, None, 5, request) + + ''' HOOK API ''' diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/helper.py b/ansible_collections/community/general/tests/unit/plugins/modules/helper.py new file mode 100644 index 000000000..a7322bf4d --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/modules/helper.py @@ -0,0 +1,181 @@ +# 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 sys +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 _BaseContext(object): + def __init__(self, helper, testcase, mocker, capfd): + self.helper = helper + self.testcase = testcase + self.mocker = mocker + self.capfd = capfd + + 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() + if test_flags.get("xfail"): + pytest.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]) + + +class _RunCmdContext(_BaseContext): + def __init__(self, *args, **kwargs): + super(_RunCmdContext, self).__init__(*args, **kwargs) + 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 check_results(self, results): + super(_RunCmdContext, self).check_results(results) + 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), "{0} != {1}".format(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 + + +class Helper(object): + @staticmethod + def from_list(module_main, list_): + helper = Helper(module_main, test_cases=list_) + return helper + + @staticmethod + def from_file(module_main, filename): + with open(filename, "r") as test_cases: + helper = Helper(module_main, test_cases=test_cases) + return helper + + @staticmethod + def from_module(module, test_module_name): + basename = module.__name__.split(".")[-1] + test_spec = "tests/unit/plugins/modules/test_{0}.yaml".format(basename) + helper = Helper.from_file(module.main, test_spec) + + setattr(sys.modules[test_module_name], "patch_bin", helper.cmd_fixture) + setattr(sys.modules[test_module_name], "test_module", helper.test_module) + + 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 _RunCmdContext(self, *args, **kwargs) + + @property + def test_module(self): + helper = self + + @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 listed in TEST_CASES + """ + + with helper(testcase, mocker, capfd) as testcase_context: + testcase_context.run() + + return _test_module diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_cpanm.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_cpanm.py index 5367a1fab..4eecf000f 100644 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_cpanm.py +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_cpanm.py @@ -12,282 +12,9 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -import json from ansible_collections.community.general.plugins.modules import cpanm +from .helper import Helper -import pytest -TESTED_MODULE = cpanm.__name__ - - -@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] - - -@pytest.mark.parametrize('patch_ansible_module, testcase', - TEST_CASES, - ids=TEST_CASES_IDS, - indirect=['patch_ansible_module']) -@pytest.mark.usefixtures('patch_ansible_module') -def test_cpanm(mocker, capfd, patch_cpanm, testcase): - """ - Run unit tests for test cases listen 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'] +Helper.from_module(cpanm, __name__) diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_cpanm.yaml b/ansible_collections/community/general/tests/unit/plugins/modules/test_cpanm.yaml new file mode 100644 index 000000000..3ed718d48 --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_cpanm.yaml @@ -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: &env-def-false {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false} + rc: 2 + out: "" + err: "error, not installed" + - command: [/testbin/cpanm, Dancer] + environ: &env-def-true {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: *env-def-false + rc: 0 + out: "" + err: "" +- id: install_dancer + input: + name: Dancer + mode: new + output: + changed: true + run_command_calls: + - command: [/testbin/cpanm, Dancer] + environ: *env-def-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: *env-def-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: *env-def-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: *env-def-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: *env-def-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: *env-def-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: *env-def-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: *env-def-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: *env-def-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: *env-def-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: *env-def-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: *env-def-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: [] diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_dnf_config_manager.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_dnf_config_manager.py new file mode 100644 index 000000000..90bffe436 --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_dnf_config_manager.py @@ -0,0 +1,402 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2023, Andrew Hyatt <andy@hyatt.xyz> +# 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.mock import patch, call +from ansible_collections.community.general.plugins.modules import dnf_config_manager as dnf_config_manager_module +from ansible_collections.community.general.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson, \ + ModuleTestCase, set_module_args + +# Return value on all-default arguments +mock_repolist_crb_enabled = """Loaded plugins: builddep, changelog, config-manager, copr, debug, debuginfo-install +DNF version: 4.14.0 +cachedir: /var/cache/dnf +Last metadata expiration check: 1:20:49 ago on Fri 22 Dec 2023 06:05:13 PM UTC. +Repo-id : appstream +Repo-name : AlmaLinux 9 - AppStream +Repo-status : enabled +Repo-revision : 1703240474 +Repo-updated : Fri 22 Dec 2023 10:21:14 AM UTC +Repo-pkgs : 5,897 +Repo-available-pkgs: 5,728 +Repo-size : 9.5 G +Repo-mirrors : https://mirrors.almalinux.org/mirrorlist/9/appstream +Repo-baseurl : http://mirror.cogentco.com/pub/linux/almalinux/9.3/AppStream/x86_64/os/ (9 more) +Repo-expire : 86,400 second(s) (last: Fri 22 Dec 2023 06:05:11 PM UTC) +Repo-filename : /etc/yum.repos.d/almalinux-appstream.repo + +Repo-id : appstream-debuginfo +Repo-name : AlmaLinux 9 - AppStream - Debug +Repo-status : disabled +Repo-mirrors : https://mirrors.almalinux.org/mirrorlist/9/appstream-debug +Repo-expire : 86,400 second(s) (last: unknown) +Repo-filename : /etc/yum.repos.d/almalinux-appstream.repo + +Repo-id : appstream-source +Repo-name : AlmaLinux 9 - AppStream - Source +Repo-status : disabled +Repo-mirrors : https://mirrors.almalinux.org/mirrorlist/9/appstream-source +Repo-expire : 86,400 second(s) (last: unknown) +Repo-filename : /etc/yum.repos.d/almalinux-appstream.repo + +Repo-id : baseos +Repo-name : AlmaLinux 9 - BaseOS +Repo-status : enabled +Repo-revision : 1703240561 +Repo-updated : Fri 22 Dec 2023 10:22:41 AM UTC +Repo-pkgs : 1,244 +Repo-available-pkgs: 1,244 +Repo-size : 1.3 G +Repo-mirrors : https://mirrors.almalinux.org/mirrorlist/9/baseos +Repo-baseurl : http://mirror.cogentco.com/pub/linux/almalinux/9.3/BaseOS/x86_64/os/ (9 more) +Repo-expire : 86,400 second(s) (last: Fri 22 Dec 2023 06:05:11 PM UTC) +Repo-filename : /etc/yum.repos.d/almalinux-baseos.repo + +Repo-id : baseos-debuginfo +Repo-name : AlmaLinux 9 - BaseOS - Debug +Repo-status : disabled +Repo-mirrors : https://mirrors.almalinux.org/mirrorlist/9/baseos-debug +Repo-expire : 86,400 second(s) (last: unknown) +Repo-filename : /etc/yum.repos.d/almalinux-baseos.repo + +Repo-id : baseos-source +Repo-name : AlmaLinux 9 - BaseOS - Source +Repo-status : disabled +Repo-mirrors : https://mirrors.almalinux.org/mirrorlist/9/baseos-source +Repo-expire : 86,400 second(s) (last: unknown) +Repo-filename : /etc/yum.repos.d/almalinux-baseos.repo + +Repo-id : copr:copr.fedorainfracloud.org:uriesk:dracut-crypt-ssh +Repo-name : Copr repo for dracut-crypt-ssh owned by uriesk +Repo-status : enabled +Repo-revision : 1698291016 +Repo-updated : Thu 26 Oct 2023 03:30:16 AM UTC +Repo-pkgs : 4 +Repo-available-pkgs: 4 +Repo-size : 102 k +Repo-baseurl : https://download.copr.fedorainfracloud.org/results/uriesk/dracut-crypt-ssh/epel-9-x86_64/ +Repo-expire : 172,800 second(s) (last: Fri 22 Dec 2023 06:05:10 PM UTC) +Repo-filename : /etc/yum.repos.d/_copr:copr.fedorainfracloud.org:uriesk:dracut-crypt-ssh.repo + +Repo-id : crb +Repo-name : AlmaLinux 9 - CRB +Repo-status : enabled +Repo-revision : 1703240590 +Repo-updated : Fri 22 Dec 2023 10:23:10 AM UTC +Repo-pkgs : 1,730 +Repo-available-pkgs: 1,727 +Repo-size : 13 G +Repo-mirrors : https://mirrors.almalinux.org/mirrorlist/9/crb +Repo-baseurl : http://mirror.cogentco.com/pub/linux/almalinux/9.3/CRB/x86_64/os/ (9 more) +Repo-expire : 86,400 second(s) (last: Fri 22 Dec 2023 06:05:11 PM UTC) +Repo-filename : /etc/yum.repos.d/almalinux-crb.repo + +Repo-id : rpmfusion-nonfree-updates +Repo-name : RPM Fusion for EL 9 - Nonfree - Updates +Repo-status : enabled +Repo-revision : 1703248251 +Repo-tags : binary-x86_64 +Repo-updated : Fri 22 Dec 2023 12:30:53 PM UTC +Repo-pkgs : 65 +Repo-available-pkgs: 65 +Repo-size : 944 M +Repo-metalink : http://mirrors.rpmfusion.org/metalink?repo=nonfree-el-updates-released-9&arch=x86_64 + Updated : Fri 22 Dec 2023 06:05:13 PM UTC +Repo-baseurl : http://uvermont.mm.fcix.net/rpmfusion/nonfree/el/updates/9/x86_64/ (33 more) +Repo-expire : 172,800 second(s) (last: Fri 22 Dec 2023 06:05:13 PM UTC) +Repo-filename : /etc/yum.repos.d/rpmfusion-nonfree-updates.repo +Total packages: 28,170 +""" + +mock_repolist_crb_disabled = """Loaded plugins: builddep, changelog, config-manager, copr, debug, debuginfo-install +DNF version: 4.14.0 +cachedir: /var/cache/dnf +Last metadata expiration check: 1:20:49 ago on Fri 22 Dec 2023 06:05:13 PM UTC. +Repo-id : appstream +Repo-name : AlmaLinux 9 - AppStream +Repo-status : enabled +Repo-revision : 1703240474 +Repo-updated : Fri 22 Dec 2023 10:21:14 AM UTC +Repo-pkgs : 5,897 +Repo-available-pkgs: 5,728 +Repo-size : 9.5 G +Repo-mirrors : https://mirrors.almalinux.org/mirrorlist/9/appstream +Repo-baseurl : http://mirror.cogentco.com/pub/linux/almalinux/9.3/AppStream/x86_64/os/ (9 more) +Repo-expire : 86,400 second(s) (last: Fri 22 Dec 2023 06:05:11 PM UTC) +Repo-filename : /etc/yum.repos.d/almalinux-appstream.repo + +Repo-id : appstream-debuginfo +Repo-name : AlmaLinux 9 - AppStream - Debug +Repo-status : disabled +Repo-mirrors : https://mirrors.almalinux.org/mirrorlist/9/appstream-debug +Repo-expire : 86,400 second(s) (last: unknown) +Repo-filename : /etc/yum.repos.d/almalinux-appstream.repo + +Repo-id : appstream-source +Repo-name : AlmaLinux 9 - AppStream - Source +Repo-status : disabled +Repo-mirrors : https://mirrors.almalinux.org/mirrorlist/9/appstream-source +Repo-expire : 86,400 second(s) (last: unknown) +Repo-filename : /etc/yum.repos.d/almalinux-appstream.repo + +Repo-id : baseos +Repo-name : AlmaLinux 9 - BaseOS +Repo-status : enabled +Repo-revision : 1703240561 +Repo-updated : Fri 22 Dec 2023 10:22:41 AM UTC +Repo-pkgs : 1,244 +Repo-available-pkgs: 1,244 +Repo-size : 1.3 G +Repo-mirrors : https://mirrors.almalinux.org/mirrorlist/9/baseos +Repo-baseurl : http://mirror.cogentco.com/pub/linux/almalinux/9.3/BaseOS/x86_64/os/ (9 more) +Repo-expire : 86,400 second(s) (last: Fri 22 Dec 2023 06:05:11 PM UTC) +Repo-filename : /etc/yum.repos.d/almalinux-baseos.repo + +Repo-id : baseos-debuginfo +Repo-name : AlmaLinux 9 - BaseOS - Debug +Repo-status : disabled +Repo-mirrors : https://mirrors.almalinux.org/mirrorlist/9/baseos-debug +Repo-expire : 86,400 second(s) (last: unknown) +Repo-filename : /etc/yum.repos.d/almalinux-baseos.repo + +Repo-id : baseos-source +Repo-name : AlmaLinux 9 - BaseOS - Source +Repo-status : disabled +Repo-mirrors : https://mirrors.almalinux.org/mirrorlist/9/baseos-source +Repo-expire : 86,400 second(s) (last: unknown) +Repo-filename : /etc/yum.repos.d/almalinux-baseos.repo + +Repo-id : copr:copr.fedorainfracloud.org:uriesk:dracut-crypt-ssh +Repo-name : Copr repo for dracut-crypt-ssh owned by uriesk +Repo-status : enabled +Repo-revision : 1698291016 +Repo-updated : Thu 26 Oct 2023 03:30:16 AM UTC +Repo-pkgs : 4 +Repo-available-pkgs: 4 +Repo-size : 102 k +Repo-baseurl : https://download.copr.fedorainfracloud.org/results/uriesk/dracut-crypt-ssh/epel-9-x86_64/ +Repo-expire : 172,800 second(s) (last: Fri 22 Dec 2023 06:05:10 PM UTC) +Repo-filename : /etc/yum.repos.d/_copr:copr.fedorainfracloud.org:uriesk:dracut-crypt-ssh.repo + +Repo-id : crb +Repo-name : AlmaLinux 9 - CRB +Repo-status : disabled +Repo-revision : 1703240590 +Repo-updated : Fri 22 Dec 2023 10:23:10 AM UTC +Repo-pkgs : 1,730 +Repo-available-pkgs: 1,727 +Repo-size : 13 G +Repo-mirrors : https://mirrors.almalinux.org/mirrorlist/9/crb +Repo-baseurl : http://mirror.cogentco.com/pub/linux/almalinux/9.3/CRB/x86_64/os/ (9 more) +Repo-expire : 86,400 second(s) (last: Fri 22 Dec 2023 06:05:11 PM UTC) +Repo-filename : /etc/yum.repos.d/almalinux-crb.repo + +Repo-id : rpmfusion-nonfree-updates +Repo-name : RPM Fusion for EL 9 - Nonfree - Updates +Repo-status : enabled +Repo-revision : 1703248251 +Repo-tags : binary-x86_64 +Repo-updated : Fri 22 Dec 2023 12:30:53 PM UTC +Repo-pkgs : 65 +Repo-available-pkgs: 65 +Repo-size : 944 M +Repo-metalink : http://mirrors.rpmfusion.org/metalink?repo=nonfree-el-updates-released-9&arch=x86_64 + Updated : Fri 22 Dec 2023 06:05:13 PM UTC +Repo-baseurl : http://uvermont.mm.fcix.net/rpmfusion/nonfree/el/updates/9/x86_64/ (33 more) +Repo-expire : 172,800 second(s) (last: Fri 22 Dec 2023 06:05:13 PM UTC) +Repo-filename : /etc/yum.repos.d/rpmfusion-nonfree-updates.repo +Total packages: 28,170 +""" + +mock_repolist_no_status = """Repo-id : appstream-debuginfo +Repo-name : AlmaLinux 9 - AppStream - Debug +Repo-mirrors : https://mirrors.almalinux.org/mirrorlist/9/appstream-debug +Repo-expire : 86,400 second(s) (last: unknown) +Repo-filename : /etc/yum.repos.d/almalinux-appstream.repo + +Repo-id : appstream-source +Repo-name : AlmaLinux 9 - AppStream - Source +Repo-status : disabled +Repo-mirrors : https://mirrors.almalinux.org/mirrorlist/9/appstream-source +Repo-expire : 86,400 second(s) (last: unknown) +Repo-filename : /etc/yum.repos.d/almalinux-appstream.repo +""" + +mock_repolist_status_before_id = """ +Repo-id : appstream-debuginfo +Repo-status : disabled +Repo-status : disabled +""" + +expected_repo_states_crb_enabled = {'disabled': ['appstream-debuginfo', + 'appstream-source', + 'baseos-debuginfo', + 'baseos-source'], + 'enabled': ['appstream', + 'baseos', + 'copr:copr.fedorainfracloud.org:uriesk:dracut-crypt-ssh', + 'crb', + 'rpmfusion-nonfree-updates']} + +expected_repo_states_crb_disabled = {'disabled': ['appstream-debuginfo', + 'appstream-source', + 'baseos-debuginfo', + 'baseos-source', + 'crb'], + 'enabled': ['appstream', + 'baseos', + 'copr:copr.fedorainfracloud.org:uriesk:dracut-crypt-ssh', + 'rpmfusion-nonfree-updates']} + +call_get_repo_states = call(['/usr/bin/dnf', 'repolist', '--all', '--verbose'], check_rc=True) +call_disable_crb = call(['/usr/bin/dnf', 'config-manager', '--set-disabled', 'crb'], check_rc=True) +call_enable_crb = call(['/usr/bin/dnf', 'config-manager', '--set-enabled', 'crb'], check_rc=True) + + +class TestDNFConfigManager(ModuleTestCase): + def setUp(self): + super(TestDNFConfigManager, self).setUp() + self.mock_run_command = (patch('ansible.module_utils.basic.AnsibleModule.run_command')) + self.run_command = self.mock_run_command.start() + self.mock_path_exists = (patch('os.path.exists')) + self.path_exists = self.mock_path_exists.start() + self.path_exists.return_value = True + self.module = dnf_config_manager_module + + def tearDown(self): + super(TestDNFConfigManager, self).tearDown() + self.mock_run_command.stop() + self.mock_path_exists.stop() + + def set_command_mock(self, execute_return=(0, '', ''), execute_side_effect=None): + self.run_command.reset_mock() + self.run_command.return_value = execute_return + self.run_command.side_effect = execute_side_effect + + def execute_module(self, failed=False, changed=False): + if failed: + result = self.failed() + self.assertTrue(result['failed']) + else: + result = self.changed(changed) + self.assertEqual(result['changed'], changed) + + return result + + def failed(self): + with self.assertRaises(AnsibleFailJson) as exc: + self.module.main() + + result = exc.exception.args[0] + self.assertTrue(result['failed']) + return result + + def changed(self, changed=False): + with self.assertRaises(AnsibleExitJson) as exc: + self.module.main() + + result = exc.exception.args[0] + self.assertEqual(result['changed'], changed) + return result + + def test_get_repo_states(self): + set_module_args({}) + self.set_command_mock(execute_return=(0, mock_repolist_crb_enabled, '')) + result = self.execute_module(changed=False) + self.assertEqual(result['repo_states_pre'], expected_repo_states_crb_enabled) + self.assertEqual(result['repo_states_post'], expected_repo_states_crb_enabled) + self.assertEqual(result['changed_repos'], []) + self.run_command.assert_has_calls(calls=[call_get_repo_states, call_get_repo_states], any_order=False) + + def test_enable_disabled_repo(self): + set_module_args({ + 'name': ['crb'], + 'state': 'enabled' + }) + side_effects = [(0, mock_repolist_crb_disabled, ''), (0, '', ''), (0, mock_repolist_crb_enabled, '')] + self.set_command_mock(execute_side_effect=side_effects) + result = self.execute_module(changed=True) + self.assertEqual(result['repo_states_pre'], expected_repo_states_crb_disabled) + self.assertEqual(result['repo_states_post'], expected_repo_states_crb_enabled) + self.assertEqual(result['changed_repos'], ['crb']) + expected_calls = [call_get_repo_states, call_enable_crb, call_get_repo_states] + self.run_command.assert_has_calls(calls=expected_calls, any_order=False) + + def test_enable_disabled_repo_check_mode(self): + set_module_args({ + 'name': ['crb'], + 'state': 'enabled', + '_ansible_check_mode': True + }) + side_effects = [(0, mock_repolist_crb_disabled, ''), (0, mock_repolist_crb_disabled, '')] + self.set_command_mock(execute_side_effect=side_effects) + result = self.execute_module(changed=True) + self.assertEqual(result['changed_repos'], ['crb']) + self.run_command.assert_has_calls(calls=[call_get_repo_states], any_order=False) + + def test_disable_enabled_repo(self): + set_module_args({ + 'name': ['crb'], + 'state': 'disabled' + }) + side_effects = [(0, mock_repolist_crb_enabled, ''), (0, '', ''), (0, mock_repolist_crb_disabled, '')] + self.set_command_mock(execute_side_effect=side_effects) + result = self.execute_module(changed=True) + self.assertEqual(result['repo_states_pre'], expected_repo_states_crb_enabled) + self.assertEqual(result['repo_states_post'], expected_repo_states_crb_disabled) + self.assertEqual(result['changed_repos'], ['crb']) + expected_calls = [call_get_repo_states, call_disable_crb, call_get_repo_states] + self.run_command.assert_has_calls(calls=expected_calls, any_order=False) + + def test_crb_already_enabled(self): + set_module_args({ + 'name': ['crb'], + 'state': 'enabled' + }) + side_effects = [(0, mock_repolist_crb_enabled, ''), (0, mock_repolist_crb_enabled, '')] + self.set_command_mock(execute_side_effect=side_effects) + result = self.execute_module(changed=False) + self.assertEqual(result['repo_states_pre'], expected_repo_states_crb_enabled) + self.assertEqual(result['repo_states_post'], expected_repo_states_crb_enabled) + self.assertEqual(result['changed_repos'], []) + self.run_command.assert_has_calls(calls=[call_get_repo_states, call_get_repo_states], any_order=False) + + def test_get_repo_states_fail_no_status(self): + set_module_args({}) + self.set_command_mock(execute_return=(0, mock_repolist_no_status, '')) + result = self.execute_module(failed=True) + self.assertEqual(result['msg'], 'dnf repolist parse failure: parsed another repo id before next status') + self.run_command.assert_has_calls(calls=[call_get_repo_states], any_order=False) + + def test_get_repo_states_fail_status_before_id(self): + set_module_args({}) + self.set_command_mock(execute_return=(0, mock_repolist_status_before_id, '')) + result = self.execute_module(failed=True) + self.assertEqual(result['msg'], 'dnf repolist parse failure: parsed status before repo id') + self.run_command.assert_has_calls(calls=[call_get_repo_states], any_order=False) + + def test_failed__unknown_repo_id(self): + set_module_args({ + 'name': ['fake'] + }) + self.set_command_mock(execute_return=(0, mock_repolist_crb_disabled, '')) + result = self.execute_module(failed=True) + self.assertEqual(result['msg'], "did not find repo with ID 'fake' in dnf repolist --all --verbose") + self.run_command.assert_has_calls(calls=[call_get_repo_states], any_order=False) + + def test_failed_state_change_ineffective(self): + set_module_args({ + 'name': ['crb'], + 'state': 'enabled' + }) + side_effects = [(0, mock_repolist_crb_disabled, ''), (0, '', ''), (0, mock_repolist_crb_disabled, '')] + self.set_command_mock(execute_side_effect=side_effects) + result = self.execute_module(failed=True) + self.assertEqual(result['msg'], "dnf config-manager failed to make 'crb' enabled") + expected_calls = [call_get_repo_states, call_enable_crb, call_get_repo_states] + self.run_command.assert_has_calls(calls=expected_calls, any_order=False) diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_dnsimple.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_dnsimple.py index 95a78818d..d5578252d 100644 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_dnsimple.py +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_dnsimple.py @@ -45,7 +45,7 @@ class TestDNSimple(ModuleTestCase): def test_account_token(self, mock_whoami): mock_whoami.return_value.data.account = 42 ds = self.module.DNSimpleV2('fake', 'fake', True, self.module) - self.assertEquals(ds.account, 42) + self.assertEqual(ds.account, 42) @patch('dnsimple.service.Accounts.list_accounts') @patch('dnsimple.service.Identity.whoami') @@ -61,4 +61,4 @@ class TestDNSimple(ModuleTestCase): mock_accounts.return_value.data = [42] mock_whoami.return_value.data.account = None ds = self.module.DNSimpleV2('fake', 'fake', True, self.module) - self.assertEquals(ds.account, 42) + self.assertEqual(ds.account, 42) diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_dnsimple_info.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_dnsimple_info.py index 5806ec772..08c5296c8 100644 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_dnsimple_info.py +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_dnsimple_info.py @@ -89,7 +89,7 @@ class TestDNSimple_Info(ModuleTestCase): result = exc_info.exception.args[0] # nothing should change self.assertFalse(result['changed']) - # we should return at least one item with mathing domain + # we should return at least one item with matching domain assert result['dnsimple_records_info'][0]['name'] == name @with_httmock(records_resp) diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_facter_facts.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_facter_facts.py new file mode 100644 index 000000000..227d8cd15 --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_facter_facts.py @@ -0,0 +1,14 @@ +# -*- 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 + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +from ansible_collections.community.general.plugins.modules import facter_facts +from .helper import Helper + + +Helper.from_module(facter_facts, __name__) diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_facter_facts.yaml b/ansible_collections/community/general/tests/unit/plugins/modules/test_facter_facts.yaml new file mode 100644 index 000000000..c287fdcfd --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_facter_facts.yaml @@ -0,0 +1,40 @@ +# -*- 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: simple run + output: + ansible_facts: + facter: + a: 1 + b: 2 + c: 3 + run_command_calls: + - command: [/testbin/facter, --json] + environ: &env-def {check_rc: true} + rc: 0 + out: > + { "a": 1, "b": 2, "c": 3 } + err: "" +- id: with args + input: + arguments: + - -p + - system_uptime + - timezone + - is_virtual + output: + ansible_facts: + facter: + a: 1 + b: 2 + c: 3 + run_command_calls: + - command: [/testbin/facter, --json, -p, system_uptime, timezone, is_virtual] + environ: *env-def + rc: 0 + out: > + { "a": 1, "b": 2, "c": 3 } + err: "" diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_gconftool2.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_gconftool2.py index f01f15ef8..9608016e5 100644 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_gconftool2.py +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_gconftool2.py @@ -6,111 +6,9 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -import json from ansible_collections.community.general.plugins.modules import gconftool2 +from .helper import Helper -import pytest -TESTED_MODULE = gconftool2.__name__ - - -@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', '',), - ), - ], - 'new_value': '100', - } - ], - [ - {'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",), - ), - ], - 'new_value': None, - } - ], - [ - {'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, '200\n', '',), - ), - ], - 'new_value': '200', - } - ], -] -TEST_CASES_IDS = [item[1]['id'] for item in TEST_CASES] - - -@pytest.mark.parametrize('patch_ansible_module, testcase', - TEST_CASES, - ids=TEST_CASES_IDS, - indirect=['patch_ansible_module']) -@pytest.mark.usefixtures('patch_ansible_module') -def test_gconftool2(mocker, capfd, patch_gconftool2, testcase): - """ - Run unit tests for test cases listen 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) - - 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 +Helper.from_module(gconftool2, __name__) diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_gconftool2.yaml b/ansible_collections/community/general/tests/unit/plugins/modules/test_gconftool2.yaml new file mode 100644 index 000000000..5114dc45f --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_gconftool2.yaml @@ -0,0 +1,117 @@ +# -*- 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_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: &env-def {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: *env-def + rc: 0 + out: "" + err: "" + - command: [/testbin/gconftool-2, --get, /desktop/gnome/background/picture_filename] + environ: *env-def + 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: *env-def + rc: 0 + out: "200\n" + err: "" + - command: [/testbin/gconftool-2, --type, int, --set, /desktop/gnome/background/picture_filename, "200"] + environ: *env-def + rc: 0 + out: "" + err: "" + - command: [/testbin/gconftool-2, --get, /desktop/gnome/background/picture_filename] + environ: *env-def + 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: *env-def + rc: 0 + out: "false\n" + err: "" + - command: [/testbin/gconftool-2, --type, bool, --set, /apps/gnome_settings_daemon/screensaver/start_screensaver, "False"] + environ: *env-def + rc: 0 + out: "" + err: "" + - command: [/testbin/gconftool-2, --get, /apps/gnome_settings_daemon/screensaver/start_screensaver] + environ: *env-def + 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: *env-def + rc: 0 + out: "200\n" + err: "" + - command: [/testbin/gconftool-2, --unset, /desktop/gnome/background/picture_filename] + environ: *env-def + 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: *env-def + rc: 0 + out: "" + err: "" + - command: [/testbin/gconftool-2, --unset, /apps/gnome_settings_daemon/screensaver/start_screensaver] + environ: *env-def + rc: 0 + out: "" + err: "" diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_gconftool2_info.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_gconftool2_info.py index 352af6bb0..54676a12d 100644 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_gconftool2_info.py +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_gconftool2_info.py @@ -6,98 +6,9 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -import json from ansible_collections.community.general.plugins.modules import gconftool2_info +from .helper import Helper -import pytest -TESTED_MODULE = gconftool2_info.__name__ - - -@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] - - -@pytest.mark.parametrize('patch_ansible_module, testcase', - TEST_CASES, - ids=TEST_CASES_IDS, - indirect=['patch_ansible_module']) -@pytest.mark.usefixtures('patch_ansible_module') -def test_gconftool2_info(mocker, capfd, patch_gconftool2_info, testcase): - """ - Run unit tests for test cases listen 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 +Helper.from_module(gconftool2_info, __name__) diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_gconftool2_info.yaml b/ansible_collections/community/general/tests/unit/plugins/modules/test_gconftool2_info.yaml new file mode 100644 index 000000000..eb8bef750 --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_gconftool2_info.yaml @@ -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: &env-def {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: *env-def + rc: 0 + out: "" + err: "No value set for `/desktop/gnome/background/picture_filename'\n" diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_gem.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_gem.py index 92578e062..10c03e537 100644 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_gem.py +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_gem.py @@ -69,7 +69,7 @@ class TestGem(ModuleTestCase): assert result['msg'] == "install_dir requires user_install=false" def test_passes_install_dir_to_gem(self): - # XXX: This test is extremely fragile, and makes assuptions about the module code, and how + # XXX: This test is extremely fragile, and makes assumptions about the module code, and how # functions are run. # If you start modifying the code of the module, you might need to modify what this # test mocks. The only thing that matters is the assertion that this 'gem install' is diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_gio_mime.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_gio_mime.py new file mode 100644 index 000000000..f2402ac35 --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_gio_mime.py @@ -0,0 +1,14 @@ +# -*- 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 + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +from ansible_collections.community.general.plugins.modules import gio_mime +from .helper import Helper + + +Helper.from_module(gio_mime, __name__) diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_gio_mime.yaml b/ansible_collections/community/general/tests/unit/plugins/modules/test_gio_mime.yaml new file mode 100644 index 000000000..d9e47a60e --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_gio_mime.yaml @@ -0,0 +1,70 @@ +# -*- 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_set_handler + input: + handler: google-chrome.desktop + mime_type: x-scheme-handler/http + output: + handler: google-chrome.desktop + changed: true + run_command_calls: + - command: [/testbin/gio, mime, x-scheme-handler/http] + environ: &env-def {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true} + rc: 0 + out: "" + err: > + No default applications for “x-scheme-handler/http” + - command: [/testbin/gio, mime, x-scheme-handler/http, google-chrome.desktop] + environ: *env-def + rc: 0 + out: "Set google-chrome.desktop as the default for x-scheme-handler/http\n" + err: "" +- id: test_set_handler_check + input: + handler: google-chrome.desktop + mime_type: x-scheme-handler/http + output: + handler: google-chrome.desktop + changed: true + flags: + skip: test helper does not support check mode yet + run_command_calls: + - command: [/testbin/gio, mime, x-scheme-handler/http] + environ: *env-def + rc: 0 + out: "" + err: > + No default applications for “x-scheme-handler/http” + - command: [/testbin/gio, mime, x-scheme-handler/http, google-chrome.desktop] + environ: *env-def + rc: 0 + out: "Set google-chrome.desktop as the default for x-scheme-handler/http\n" + err: "" +- id: test_set_handler_idempot + input: + handler: google-chrome.desktop + mime_type: x-scheme-handler/http + output: + handler: google-chrome.desktop + changed: false + run_command_calls: + - command: [/testbin/gio, mime, x-scheme-handler/http] + environ: *env-def + rc: 0 + out: | + Default application for “x-scheme-handler/https”: google-chrome.desktop + Registered applications: + brave-browser.desktop + firefox.desktop + google-chrome.desktop + firefox_firefox.desktop + Recommended applications: + brave-browser.desktop + firefox.desktop + google-chrome.desktop + firefox_firefox.desktop + err: "" diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_gitlab_group_access_token.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_gitlab_group_access_token.py new file mode 100644 index 000000000..06af94820 --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_gitlab_group_access_token.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2023, Zoran Krleza (zoran.krleza@true-north.hr) +# 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 pytest +import gitlab + +from ansible_collections.community.general.plugins.modules.gitlab_group_access_token import GitLabGroupAccessToken + +# python-gitlab 3.1+ is needed for python-gitlab access tokens api +PYTHON_GITLAB_MINIMAL_VERSION = (3, 1) + + +def python_gitlab_version_match_requirement(): + return tuple(map(int, gitlab.__version__.split('.'))) >= PYTHON_GITLAB_MINIMAL_VERSION + + +def _dummy(x): + """Dummy function. Only used as a placeholder for toplevel definitions when the test is going + to be skipped anyway""" + return x + + +pytestmark = [] +try: + from .gitlab import (GitlabModuleTestCase, + resp_get_user, + resp_get_group, + resp_list_group_access_tokens, + resp_create_group_access_tokens, + resp_revoke_group_access_tokens) + +except ImportError: + pytestmark.append(pytest.mark.skip("Could not load gitlab module required for testing")) + # Need to set these to something so that we don't fail when parsing + GitlabModuleTestCase = object + resp_list_group_access_tokens = _dummy + resp_create_group_access_tokens = _dummy + resp_revoke_group_access_tokens = _dummy + resp_get_user = _dummy + resp_get_group = _dummy + +# Unit tests requirements +try: + from httmock import with_httmock # noqa +except ImportError: + pytestmark.append(pytest.mark.skip("Could not load httmock module required for testing")) + with_httmock = _dummy + + +class TestGitlabGroupAccessToken(GitlabModuleTestCase): + @with_httmock(resp_get_user) + def setUp(self): + super(TestGitlabGroupAccessToken, self).setUp() + if not python_gitlab_version_match_requirement(): + self.skipTest("python-gitlab %s+ is needed for gitlab_group_access_token" % ",".join(map(str, PYTHON_GITLAB_MINIMAL_VERSION))) + + self.moduleUtil = GitLabGroupAccessToken(module=self.mock_module, gitlab_instance=self.gitlab_instance) + + @with_httmock(resp_get_group) + @with_httmock(resp_list_group_access_tokens) + def test_find_access_token(self): + group = self.gitlab_instance.groups.get(1) + self.assertIsNotNone(group) + + rvalue = self.moduleUtil.find_access_token(group, "token1") + self.assertEqual(rvalue, False) + self.assertIsNotNone(self.moduleUtil.access_token_object) + + @with_httmock(resp_get_group) + @with_httmock(resp_list_group_access_tokens) + def test_find_access_token_negative(self): + groups = self.gitlab_instance.groups.get(1) + self.assertIsNotNone(groups) + + rvalue = self.moduleUtil.find_access_token(groups, "nonexisting") + self.assertEqual(rvalue, False) + self.assertIsNone(self.moduleUtil.access_token_object) + + @with_httmock(resp_get_group) + @with_httmock(resp_create_group_access_tokens) + def test_create_access_token(self): + groups = self.gitlab_instance.groups.get(1) + self.assertIsNotNone(groups) + + rvalue = self.moduleUtil.create_access_token(groups, {'name': "tokenXYZ", 'scopes': ["api"], 'access_level': 20, 'expires_at': "2024-12-31"}) + self.assertEqual(rvalue, True) + self.assertIsNotNone(self.moduleUtil.access_token_object) + + @with_httmock(resp_get_group) + @with_httmock(resp_list_group_access_tokens) + @with_httmock(resp_revoke_group_access_tokens) + def test_revoke_access_token(self): + groups = self.gitlab_instance.groups.get(1) + self.assertIsNotNone(groups) + + rvalue = self.moduleUtil.find_access_token(groups, "token1") + self.assertEqual(rvalue, False) + self.assertIsNotNone(self.moduleUtil.access_token_object) + + rvalue = self.moduleUtil.revoke_access_token() + self.assertEqual(rvalue, True) diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_gitlab_project_access_token.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_gitlab_project_access_token.py new file mode 100644 index 000000000..ebc324b88 --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_gitlab_project_access_token.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2023, Zoran Krleza (zoran.krleza@true-north.hr) +# 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 pytest +import gitlab + +from ansible_collections.community.general.plugins.modules.gitlab_project_access_token import GitLabProjectAccessToken + +# python-gitlab 3.1+ is needed for python-gitlab access tokens api +PYTHON_GITLAB_MINIMAL_VERSION = (3, 1) + + +def python_gitlab_version_match_requirement(): + return tuple(map(int, gitlab.__version__.split('.'))) >= PYTHON_GITLAB_MINIMAL_VERSION + + +def _dummy(x): + """Dummy function. Only used as a placeholder for toplevel definitions when the test is going + to be skipped anyway""" + return x + + +pytestmark = [] +try: + from .gitlab import (GitlabModuleTestCase, + resp_get_user, + resp_get_project, + resp_list_project_access_tokens, + resp_create_project_access_tokens, + resp_revoke_project_access_tokens) + +except ImportError: + pytestmark.append(pytest.mark.skip("Could not load gitlab module required for testing")) + # Need to set these to something so that we don't fail when parsing + GitlabModuleTestCase = object + resp_list_project_access_tokens = _dummy + resp_create_project_access_tokens = _dummy + resp_revoke_project_access_tokens = _dummy + resp_get_user = _dummy + resp_get_project = _dummy + +# Unit tests requirements +try: + from httmock import with_httmock # noqa +except ImportError: + pytestmark.append(pytest.mark.skip("Could not load httmock module required for testing")) + with_httmock = _dummy + + +class TestGitlabProjectAccessToken(GitlabModuleTestCase): + @with_httmock(resp_get_user) + def setUp(self): + super(TestGitlabProjectAccessToken, self).setUp() + if not python_gitlab_version_match_requirement(): + self.skipTest("python-gitlab %s+ is needed for gitlab_project_access_token" % ",".join(map(str, PYTHON_GITLAB_MINIMAL_VERSION))) + + self.moduleUtil = GitLabProjectAccessToken(module=self.mock_module, gitlab_instance=self.gitlab_instance) + + @with_httmock(resp_get_project) + @with_httmock(resp_list_project_access_tokens) + def test_find_access_token(self): + project = self.gitlab_instance.projects.get(1) + self.assertIsNotNone(project) + + rvalue = self.moduleUtil.find_access_token(project, "token1") + self.assertEqual(rvalue, False) + self.assertIsNotNone(self.moduleUtil.access_token_object) + + @with_httmock(resp_get_project) + @with_httmock(resp_list_project_access_tokens) + def test_find_access_token_negative(self): + project = self.gitlab_instance.projects.get(1) + self.assertIsNotNone(project) + + rvalue = self.moduleUtil.find_access_token(project, "nonexisting") + self.assertEqual(rvalue, False) + self.assertIsNone(self.moduleUtil.access_token_object) + + @with_httmock(resp_get_project) + @with_httmock(resp_create_project_access_tokens) + def test_create_access_token(self): + project = self.gitlab_instance.projects.get(1) + self.assertIsNotNone(project) + + rvalue = self.moduleUtil.create_access_token(project, {'name': "tokenXYZ", 'scopes': ["api"], 'access_level': 20, 'expires_at': "2024-12-31"}) + self.assertEqual(rvalue, True) + self.assertIsNotNone(self.moduleUtil.access_token_object) + + @with_httmock(resp_get_project) + @with_httmock(resp_list_project_access_tokens) + @with_httmock(resp_revoke_project_access_tokens) + def test_revoke_access_token(self): + project = self.gitlab_instance.projects.get(1) + self.assertIsNotNone(project) + + rvalue = self.moduleUtil.find_access_token(project, "token1") + self.assertEqual(rvalue, False) + self.assertIsNotNone(self.moduleUtil.access_token_object) + + rvalue = self.moduleUtil.revoke_access_token() + self.assertEqual(rvalue, True) diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_hana_query.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_hana_query.py deleted file mode 100644 index db06e4cef..000000000 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_hana_query.py +++ /dev/null @@ -1,103 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2021, Rainer Leber (@rainerleber) <rainerleber@gmail.com> -# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) -# SPDX-License-Identifier: GPL-3.0-or-later - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - -from ansible_collections.community.general.plugins.modules import hana_query -from ansible_collections.community.general.tests.unit.plugins.modules.utils import ( - AnsibleExitJson, - AnsibleFailJson, - ModuleTestCase, - set_module_args, -) -from ansible_collections.community.general.tests.unit.compat.mock import patch -from ansible.module_utils import basic - - -def get_bin_path(*args, **kwargs): - """Function to return path of hdbsql""" - return "/usr/sap/HDB/HDB01/exe/hdbsql" - - -class Testhana_query(ModuleTestCase): - """Main class for testing hana_query module.""" - - def setUp(self): - """Setup.""" - super(Testhana_query, self).setUp() - self.module = hana_query - self.mock_get_bin_path = patch.object(basic.AnsibleModule, 'get_bin_path', get_bin_path) - self.mock_get_bin_path.start() - self.addCleanup(self.mock_get_bin_path.stop) # ensure that the patching is 'undone' - - def tearDown(self): - """Teardown.""" - super(Testhana_query, self).tearDown() - - def test_without_required_parameters(self): - """Failure must occurs when all parameters are missing.""" - with self.assertRaises(AnsibleFailJson): - set_module_args({}) - self.module.main() - - def test_hana_query(self): - """Check that result is processed.""" - set_module_args({ - 'sid': "HDB", - 'instance': "01", - 'encrypted': False, - 'host': "localhost", - 'user': "SYSTEM", - 'password': "1234Qwer", - 'database': "HDB", - 'query': "SELECT * FROM users;" - }) - with patch.object(basic.AnsibleModule, 'run_command') as run_command: - run_command.return_value = 0, 'username,name\n testuser,test user \n myuser, my user \n', '' - with self.assertRaises(AnsibleExitJson) as result: - hana_query.main() - self.assertEqual(result.exception.args[0]['query_result'], [[ - {'username': 'testuser', 'name': 'test user'}, - {'username': 'myuser', 'name': 'my user'}, - ]]) - self.assertEqual(run_command.call_count, 1) - - def test_hana_userstore_query(self): - """Check that result is processed with userstore.""" - set_module_args({ - 'sid': "HDB", - 'instance': "01", - 'encrypted': False, - 'host': "localhost", - 'user': "SYSTEM", - 'userstore': True, - 'database': "HDB", - 'query': "SELECT * FROM users;" - }) - with patch.object(basic.AnsibleModule, 'run_command') as run_command: - run_command.return_value = 0, 'username,name\n testuser,test user \n myuser, my user \n', '' - with self.assertRaises(AnsibleExitJson) as result: - hana_query.main() - self.assertEqual(result.exception.args[0]['query_result'], [[ - {'username': 'testuser', 'name': 'test user'}, - {'username': 'myuser', 'name': 'my user'}, - ]]) - self.assertEqual(run_command.call_count, 1) - - def test_hana_failed_no_passwd(self): - """Check that result is failed with no password.""" - with self.assertRaises(AnsibleFailJson): - set_module_args({ - 'sid': "HDB", - 'instance': "01", - 'encrypted': False, - 'host': "localhost", - 'user': "SYSTEM", - 'database': "HDB", - 'query': "SELECT * FROM users;" - }) - self.module.main() diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_ini_file.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_ini_file.py new file mode 100644 index 000000000..a65a9c326 --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_ini_file.py @@ -0,0 +1,51 @@ +# Copyright (c) 2023 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.plugins.modules import ini_file + + +def do_test(option, ignore_spaces, newline, before, expected_after, + expected_changed, expected_msg): + section_lines = [before] + changed_lines = [0] + changed, msg = ini_file.update_section_line( + option, None, section_lines, 0, changed_lines, ignore_spaces, + newline, None) + assert section_lines[0] == expected_after + assert changed == expected_changed + assert changed_lines[0] == 1 + assert msg == expected_msg + + +def test_ignore_spaces_comment(): + oldline = ';foobar=baz' + newline = 'foobar = baz' + do_test('foobar', True, newline, oldline, newline, True, 'option changed') + + +def test_ignore_spaces_changed(): + oldline = 'foobar=baz' + newline = 'foobar = freeble' + do_test('foobar', True, newline, oldline, newline, True, 'option changed') + + +def test_ignore_spaces_unchanged(): + oldline = 'foobar=baz' + newline = 'foobar = baz' + do_test('foobar', True, newline, oldline, oldline, False, None) + + +def test_no_ignore_spaces_changed(): + oldline = 'foobar=baz' + newline = 'foobar = baz' + do_test('foobar', False, newline, oldline, newline, True, 'option changed') + + +def test_no_ignore_spaces_unchanged(): + newline = 'foobar=baz' + do_test('foobar', False, newline, newline, newline, False, None) diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_ipa_otptoken.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_ipa_otptoken.py index c06e19c3b..23911e5a5 100644 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_ipa_otptoken.py +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_ipa_otptoken.py @@ -104,7 +104,7 @@ class TestIPAOTPToken(ModuleTestCase): { 'method': 'otptoken_add', 'name': 'NewToken1', - 'item': {'ipatokendisabled': 'FALSE', + 'item': {'ipatokendisabled': False, 'all': True} } ) @@ -130,7 +130,7 @@ class TestIPAOTPToken(ModuleTestCase): { 'method': 'otptoken_add', 'name': 'NewToken1', - 'item': {'ipatokendisabled': 'FALSE', + 'item': {'ipatokendisabled': False, 'all': True} } ) @@ -176,7 +176,7 @@ class TestIPAOTPToken(ModuleTestCase): 'ipatokenotpkey': 'KRSXG5CTMVRXEZLUGE======', 'description': 'Test description', 'ipatokenowner': 'pinky', - 'ipatokendisabled': 'FALSE', + 'ipatokendisabled': False, 'ipatokennotbefore': '20200101010101Z', 'ipatokennotafter': '20900101010101Z', 'ipatokenvendor': 'Acme', @@ -220,7 +220,7 @@ class TestIPAOTPToken(ModuleTestCase): 'ipatokenotpkey': [{'__base64__': 'VGVzdFNlY3JldDE='}], 'description': ['Test description'], 'ipatokenowner': ['pinky'], - 'ipatokendisabled': ['FALSE'], + 'ipatokendisabled': [False], 'ipatokennotbefore': ['20200101010101Z'], 'ipatokennotafter': ['20900101010101Z'], 'ipatokenvendor': ['Acme'], @@ -271,7 +271,7 @@ class TestIPAOTPToken(ModuleTestCase): 'ipatokenotpkey': [{'__base64__': 'VGVzdFNlY3JldDE='}], 'description': ['Test description'], 'ipatokenowner': ['pinky'], - 'ipatokendisabled': ['FALSE'], + 'ipatokendisabled': [False], 'ipatokennotbefore': ['20200101010101Z'], 'ipatokennotafter': ['20900101010101Z'], 'ipatokenvendor': ['Acme'], @@ -296,7 +296,7 @@ class TestIPAOTPToken(ModuleTestCase): 'name': 'NewToken1', 'item': {'description': 'Test description', 'ipatokenowner': 'brain', - 'ipatokendisabled': 'FALSE', + 'ipatokendisabled': False, 'ipatokennotbefore': '20200101010101Z', 'ipatokennotafter': '20900101010101Z', 'ipatokenvendor': 'Acme', @@ -335,7 +335,7 @@ class TestIPAOTPToken(ModuleTestCase): 'ipatokenotpkey': [{'__base64__': 'VGVzdFNlY3JldDE='}], 'description': ['Test description'], 'ipatokenowner': ['pinky'], - 'ipatokendisabled': ['FALSE'], + 'ipatokendisabled': [False], 'ipatokennotbefore': ['20200101010101Z'], 'ipatokennotafter': ['20900101010101Z'], 'ipatokenvendor': ['Acme'], @@ -360,7 +360,7 @@ class TestIPAOTPToken(ModuleTestCase): 'name': 'NewToken1', 'item': {'description': 'New Test description', 'ipatokenowner': 'pinky', - 'ipatokendisabled': 'TRUE', + 'ipatokendisabled': True, 'ipatokennotbefore': '20200101010102Z', 'ipatokennotafter': '20900101010102Z', 'ipatokenvendor': 'NewAcme', @@ -384,7 +384,7 @@ class TestIPAOTPToken(ModuleTestCase): 'ipatokenotpkey': [{'__base64__': 'KRSXG5CTMVRXEZLUGE======'}], 'description': ['Test description'], 'ipatokenowner': ['pinky'], - 'ipatokendisabled': ['FALSE'], + 'ipatokendisabled': [False], 'ipatokennotbefore': ['20200101010101Z'], 'ipatokennotafter': ['20900101010101Z'], 'ipatokenvendor': ['Acme'], @@ -425,7 +425,7 @@ class TestIPAOTPToken(ModuleTestCase): 'ipatokenotpkey': [{'__base64__': 'KRSXG5CTMVRXEZLUGE======'}], 'description': ['Test description'], 'ipatokenowner': ['pinky'], - 'ipatokendisabled': ['FALSE'], + 'ipatokendisabled': [False], 'ipatokennotbefore': ['20200101010101Z'], 'ipatokennotafter': ['20900101010101Z'], 'ipatokenvendor': ['Acme'], @@ -448,7 +448,7 @@ class TestIPAOTPToken(ModuleTestCase): { 'method': 'otptoken_mod', 'name': 'NewToken1', - 'item': {'ipatokendisabled': 'TRUE', + 'item': {'ipatokendisabled': True, 'all': True} } ) diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_ipa_pwpolicy.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_ipa_pwpolicy.py index b45c566fc..538f61e9a 100644 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_ipa_pwpolicy.py +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_ipa_pwpolicy.py @@ -100,7 +100,12 @@ class TestIPAPwPolicy(ModuleTestCase): 'minlength': '16', 'maxfailcount': '6', 'failinterval': '60', - 'lockouttime': '600' + 'lockouttime': '600', + 'gracelimit': 3, + 'maxrepeat': 3, + 'maxsequence': 3, + 'dictcheck': True, + 'usercheck': True, } return_value = {} mock_calls = ( @@ -124,7 +129,12 @@ class TestIPAPwPolicy(ModuleTestCase): 'krbpwdminlength': '16', 'krbpwdmaxfailure': '6', 'krbpwdfailurecountinterval': '60', - 'krbpwdlockoutduration': '600' + 'krbpwdlockoutduration': '600', + 'passwordgracelimit': '3', + 'ipapwdmaxrepeat': '3', + 'ipapwdmaxsequence': '3', + 'ipapwddictcheck': True, + 'ipapwdusercheck': True, } } ) @@ -145,7 +155,12 @@ class TestIPAPwPolicy(ModuleTestCase): 'minlength': '16', 'maxfailcount': '6', 'failinterval': '60', - 'lockouttime': '600' + 'lockouttime': '600', + 'gracelimit': 3, + 'maxrepeat': 3, + 'maxsequence': 3, + 'dictcheck': True, + 'usercheck': True, } return_value = {} mock_calls = ( @@ -169,7 +184,12 @@ class TestIPAPwPolicy(ModuleTestCase): 'krbpwdminlength': '16', 'krbpwdmaxfailure': '6', 'krbpwdfailurecountinterval': '60', - 'krbpwdlockoutduration': '600' + 'krbpwdlockoutduration': '600', + 'passwordgracelimit': '3', + 'ipapwdmaxrepeat': '3', + 'ipapwdmaxsequence': '3', + 'ipapwddictcheck': True, + 'ipapwdusercheck': True, } } ) @@ -190,7 +210,12 @@ class TestIPAPwPolicy(ModuleTestCase): 'minlength': '12', 'maxfailcount': '8', 'failinterval': '60', - 'lockouttime': '600' + 'lockouttime': '600', + 'gracelimit': 3, + 'maxrepeat': 3, + 'maxsequence': 3, + 'dictcheck': True, + 'usercheck': True, } return_value = { 'cn': ['sysops'], @@ -203,6 +228,11 @@ class TestIPAPwPolicy(ModuleTestCase): 'krbpwdmaxfailure': ['6'], 'krbpwdfailurecountinterval': ['60'], 'krbpwdlockoutduration': ['600'], + 'passwordgracelimit': ['3'], + 'ipapwdmaxrepeat': ['3'], + 'ipapwdmaxsequence': ['3'], + 'ipapwddictcheck': [True], + 'ipapwdusercheck': [True], 'dn': 'cn=sysops,cn=EXAMPLE.COM,cn=kerberos,dc=example,dc=com', 'objectclass': ['top', 'nscontainer', 'krbpwdpolicy'] } @@ -227,7 +257,12 @@ class TestIPAPwPolicy(ModuleTestCase): 'krbpwdminlength': '12', 'krbpwdmaxfailure': '8', 'krbpwdfailurecountinterval': '60', - 'krbpwdlockoutduration': '600' + 'krbpwdlockoutduration': '600', + 'passwordgracelimit': '3', + 'ipapwdmaxrepeat': '3', + 'ipapwdmaxsequence': '3', + 'ipapwddictcheck': True, + 'ipapwdusercheck': True, } } ) @@ -248,7 +283,12 @@ class TestIPAPwPolicy(ModuleTestCase): 'minlength': '16', 'maxfailcount': '6', 'failinterval': '60', - 'lockouttime': '600' + 'lockouttime': '600', + 'gracelimit': 3, + 'maxrepeat': 3, + 'maxsequence': 3, + 'dictcheck': True, + 'usercheck': True, } return_value = { 'cn': ['sysops'], @@ -281,7 +321,12 @@ class TestIPAPwPolicy(ModuleTestCase): 'krbpwdminlength': '16', 'krbpwdmaxfailure': '6', 'krbpwdfailurecountinterval': '60', - 'krbpwdlockoutduration': '600' + 'krbpwdlockoutduration': '600', + 'passwordgracelimit': '3', + 'ipapwdmaxrepeat': '3', + 'ipapwdmaxsequence': '3', + 'ipapwddictcheck': True, + 'ipapwdusercheck': True, } } ) @@ -342,7 +387,12 @@ class TestIPAPwPolicy(ModuleTestCase): 'minlength': '16', 'maxfailcount': '6', 'failinterval': '60', - 'lockouttime': '600' + 'lockouttime': '600', + 'gracelimit': 3, + 'maxrepeat': 3, + 'maxsequence': 3, + 'dictcheck': True, + 'usercheck': True, } return_value = { 'cn': ['admins'], @@ -355,6 +405,11 @@ class TestIPAPwPolicy(ModuleTestCase): 'krbpwdmaxfailure': ['6'], 'krbpwdfailurecountinterval': ['60'], 'krbpwdlockoutduration': ['600'], + 'passwordgracelimit': ['3'], + 'ipapwdmaxrepeat': ['3'], + 'ipapwdmaxsequence': ['3'], + 'ipapwddictcheck': [True], + 'ipapwdusercheck': [True], 'dn': 'cn=admins,cn=EXAMPLE.COM,cn=kerberos,dc=example,dc=com', 'objectclass': ['top', 'nscontainer', 'krbpwdpolicy'] } @@ -409,7 +464,12 @@ class TestIPAPwPolicy(ModuleTestCase): 'minlength': '12', 'maxfailcount': '8', 'failinterval': '60', - 'lockouttime': '600' + 'lockouttime': '600', + 'gracelimit': 3, + 'maxrepeat': 3, + 'maxsequence': 3, + 'dictcheck': True, + 'usercheck': True, } return_value = { 'cn': ['global_policy'], @@ -420,6 +480,11 @@ class TestIPAPwPolicy(ModuleTestCase): 'krbpwdmaxfailure': ['6'], 'krbpwdfailurecountinterval': ['60'], 'krbpwdlockoutduration': ['600'], + 'passwordgracelimit': ['3'], + 'ipapwdmaxrepeat': ['3'], + 'ipapwdmaxsequence': ['3'], + 'ipapwddictcheck': [True], + 'ipapwdusercheck': [True], 'dn': 'cn=global_policy,cn=EXAMPLE.COM,cn=kerberos,dc=example,dc=com', 'objectclass': ['top', 'nscontainer', 'krbpwdpolicy'] } @@ -443,7 +508,12 @@ class TestIPAPwPolicy(ModuleTestCase): 'krbpwdminlength': '12', 'krbpwdmaxfailure': '8', 'krbpwdfailurecountinterval': '60', - 'krbpwdlockoutduration': '600' + 'krbpwdlockoutduration': '600', + 'passwordgracelimit': '3', + 'ipapwdmaxrepeat': '3', + 'ipapwdmaxsequence': '3', + 'ipapwddictcheck': True, + 'ipapwdusercheck': True, } } ) @@ -461,7 +531,12 @@ class TestIPAPwPolicy(ModuleTestCase): 'minlength': '16', 'maxfailcount': '6', 'failinterval': '60', - 'lockouttime': '600' + 'lockouttime': '600', + 'gracelimit': 3, + 'maxrepeat': 3, + 'maxsequence': 3, + 'dictcheck': True, + 'usercheck': True, } return_value = { 'cn': ['global_policy'], @@ -473,6 +548,11 @@ class TestIPAPwPolicy(ModuleTestCase): 'krbpwdmaxfailure': ['6'], 'krbpwdfailurecountinterval': ['60'], 'krbpwdlockoutduration': ['600'], + 'passwordgracelimit': ['3'], + 'ipapwdmaxrepeat': ['3'], + 'ipapwdmaxsequence': ['3'], + 'ipapwddictcheck': [True], + 'ipapwdusercheck': [True], 'dn': 'cn=global_policy,cn=EXAMPLE.COM,cn=kerberos,dc=example,dc=com', 'objectclass': ['top', 'nscontainer', 'krbpwdpolicy'] } @@ -504,7 +584,12 @@ class TestIPAPwPolicy(ModuleTestCase): 'minlength': '16', 'maxfailcount': '6', 'failinterval': '60', - 'lockouttime': '600' + 'lockouttime': '600', + 'gracelimit': 3, + 'maxrepeat': 3, + 'maxsequence': 3, + 'dictcheck': True, + 'usercheck': True, } return_value = {} mock_calls = [ @@ -535,7 +620,12 @@ class TestIPAPwPolicy(ModuleTestCase): 'minlength': '12', 'maxfailcount': '8', 'failinterval': '60', - 'lockouttime': '600' + 'lockouttime': '600', + 'gracelimit': 3, + 'maxrepeat': 3, + 'maxsequence': 3, + 'dictcheck': True, + 'usercheck': True, } return_value = { 'cn': ['sysops'], @@ -548,6 +638,11 @@ class TestIPAPwPolicy(ModuleTestCase): 'krbpwdmaxfailure': ['6'], 'krbpwdfailurecountinterval': ['60'], 'krbpwdlockoutduration': ['600'], + 'passwordgracelimit': ['3'], + 'ipapwdmaxrepeat': ['3'], + 'ipapwdmaxsequence': ['3'], + 'ipapwddictcheck': [True], + 'ipapwdusercheck': [True], 'dn': 'cn=sysops,cn=EXAMPLE.COM,cn=kerberos,dc=example,dc=com', 'objectclass': ['top', 'nscontainer', 'krbpwdpolicy'] } diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_ipbase.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_ipbase.py new file mode 100644 index 000000000..8106889da --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_ipbase.py @@ -0,0 +1,187 @@ +# -*- 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 + +import json + +from ansible_collections.community.general.plugins.modules.ipbase_info import IpbaseInfo +from ansible_collections.community.general.tests.unit.compat import unittest +from ansible_collections.community.general.tests.unit.compat.mock import Mock + + +IPBASE_DATA = { + "response": b""" +{ + "data": { + "ip": "1.1.1.1", + "hostname": "one.one.one.one", + "type": "v4", + "range_type": { + "type": "PUBLIC", + "description": "Public address" + }, + "connection": { + "asn": 13335, + "organization": "Cloudflare, Inc.", + "isp": "APNIC Research and Development", + "range": "1.1.1.1/32" + }, + "location": { + "geonames_id": 5332870, + "latitude": 34.053611755371094, + "longitude": -118.24549865722656, + "zip": "90012", + "continent": { + "code": "NA", + "name": "North America", + "name_translated": "North America" + }, + "country": { + "alpha2": "US", + "alpha3": "USA", + "calling_codes": [ + "+1" + ], + "currencies": [ + { + "symbol": "$", + "name": "US Dollar", + "symbol_native": "$", + "decimal_digits": 2, + "rounding": 0, + "code": "USD", + "name_plural": "US dollars" + } + ], + "emoji": "...", + "ioc": "USA", + "languages": [ + { + "name": "English", + "name_native": "English" + } + ], + "name": "United States", + "name_translated": "United States", + "timezones": [ + "America/New_York", + "America/Detroit", + "America/Kentucky/Louisville", + "America/Kentucky/Monticello", + "America/Indiana/Indianapolis", + "America/Indiana/Vincennes", + "America/Indiana/Winamac", + "America/Indiana/Marengo", + "America/Indiana/Petersburg", + "America/Indiana/Vevay", + "America/Chicago", + "America/Indiana/Tell_City", + "America/Indiana/Knox", + "America/Menominee", + "America/North_Dakota/Center", + "America/North_Dakota/New_Salem", + "America/North_Dakota/Beulah", + "America/Denver", + "America/Boise", + "America/Phoenix", + "America/Los_Angeles", + "America/Anchorage", + "America/Juneau", + "America/Sitka", + "America/Metlakatla", + "America/Yakutat", + "America/Nome", + "America/Adak", + "Pacific/Honolulu" + ], + "is_in_european_union": false, + "fips": "US", + "geonames_id": 6252001, + "hasc_id": "US", + "wikidata_id": "Q30" + }, + "city": { + "fips": "644000", + "alpha2": null, + "geonames_id": 5368753, + "hasc_id": null, + "wikidata_id": "Q65", + "name": "Los Angeles", + "name_translated": "Los Angeles" + }, + "region": { + "fips": "US06", + "alpha2": "US-CA", + "geonames_id": 5332921, + "hasc_id": "US.CA", + "wikidata_id": "Q99", + "name": "California", + "name_translated": "California" + } + }, + "tlds": [ + ".us" + ], + "timezone": { + "id": "America/Los_Angeles", + "current_time": "2023-05-04T04:30:28-07:00", + "code": "PDT", + "is_daylight_saving": true, + "gmt_offset": -25200 + }, + "security": { + "is_anonymous": false, + "is_datacenter": false, + "is_vpn": false, + "is_bot": false, + "is_abuser": true, + "is_known_attacker": true, + "is_proxy": false, + "is_spam": false, + "is_tor": false, + "is_icloud_relay": false, + "threat_score": 100 + }, + "domains": { + "count": 10943, + "domains": [ + "eliwise.academy", + "accountingprose.academy", + "pistola.academy", + "1and1-test-ntlds-fr.accountant", + "omnergy.africa" + ] + } + } +} +""" +} + + +class TestIpbase(unittest.TestCase): + def test_info(self,): + "test the json data extraction" + + params = { + "ip": "1.1.1.1", + "apikey": "aaa", + "hostname": True, + "language": "de", + } + module = Mock() + module.params = params + + data = json.loads(IPBASE_DATA['response'].decode("utf-8")) + + IpbaseInfo._get_url_data = Mock() + IpbaseInfo._get_url_data.return_value = data + jenkins_plugin = IpbaseInfo(module) + + json_data = jenkins_plugin.info() + + self.maxDiff = None + self.assertDictEqual(json_data, data) diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_jenkins_build.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_jenkins_build.py index 44c6307ac..d9013a018 100644 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_jenkins_build.py +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_jenkins_build.py @@ -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']) diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_jenkins_build_info.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_jenkins_build_info.py new file mode 100644 index 000000000..b5d4126fe --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_jenkins_build_info.py @@ -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.assertEqual("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.assertEqual("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.assertEqual("ABSENT", return_json.exception.args[0]['build_info']['result']) diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_keycloak_authentication_required_actions.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_keycloak_authentication_required_actions.py new file mode 100644 index 000000000..2adc3a896 --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_keycloak_authentication_required_actions.py @@ -0,0 +1,835 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2021, 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 contextlib import contextmanager + +from ansible_collections.community.general.tests.unit.compat import unittest +from ansible_collections.community.general.tests.unit.compat.mock import patch +from ansible_collections.community.general.tests.unit.plugins.modules.utils import AnsibleExitJson, ModuleTestCase, set_module_args + +from ansible_collections.community.general.plugins.modules import keycloak_authentication_required_actions + +from itertools import count + +from ansible.module_utils.six import StringIO + + +@contextmanager +def patch_keycloak_api( + get_required_actions=None, + register_required_action=None, + update_required_action=None, + delete_required_action=None, +): + """ + Mock context manager for patching the methods in PwPolicyIPAClient that contact the IPA server + + Patches the `login` and `_post_json` methods + + Keyword arguments are passed to the mock object that patches `_post_json` + + No arguments are passed to the mock object that patches `login` because no tests require it + + Example:: + + with patch_ipa(return_value={}) as (mock_login, mock_post): + ... + """ + + obj = keycloak_authentication_required_actions.KeycloakAPI + with patch.object( + obj, + 'get_required_actions', + side_effect=get_required_actions + ) as mock_get_required_actions: + with patch.object( + obj, + 'register_required_action', + side_effect=register_required_action + ) as mock_register_required_action: + with patch.object( + obj, + 'update_required_action', + side_effect=update_required_action + ) as mock_update_required_action: + with patch.object( + obj, + 'delete_required_action', + side_effect=delete_required_action + ) as mock_delete_required_action: + yield ( + mock_get_required_actions, + mock_register_required_action, + mock_update_required_action, + mock_delete_required_action, + ) + + +def get_response(object_with_future_response, method, get_id_call_count): + if callable(object_with_future_response): + return object_with_future_response() + if isinstance(object_with_future_response, dict): + return get_response( + object_with_future_response[method], method, get_id_call_count) + if isinstance(object_with_future_response, list): + call_number = next(get_id_call_count) + return get_response( + object_with_future_response[call_number], method, get_id_call_count) + return object_with_future_response + + +def build_mocked_request(get_id_user_count, response_dict): + def _mocked_requests(*args, **kwargs): + url = args[0] + method = kwargs['method'] + future_response = response_dict.get(url, None) + return get_response(future_response, method, get_id_user_count) + return _mocked_requests + + +def create_wrapper(text_as_string): + """Allow to mock many times a call to one address. + Without this function, the StringIO is empty for the second call. + """ + def _create_wrapper(): + return StringIO(text_as_string) + return _create_wrapper + + +def mock_good_connection(): + token_response = { + 'http://keycloak.url/auth/realms/master/protocol/openid-connect/token': create_wrapper('{"access_token": "alongtoken"}'), } + return patch( + 'ansible_collections.community.general.plugins.module_utils.identity.keycloak.keycloak.open_url', + side_effect=build_mocked_request(count(), token_response), + autospec=True + ) + + +class TestKeycloakAuthentication(ModuleTestCase): + def setUp(self): + super(TestKeycloakAuthentication, self).setUp() + self.module = keycloak_authentication_required_actions + + def test_register_required_action(self): + """Register a new authentication required action.""" + + module_args = { + 'auth_client_id': 'admin-cli', + 'auth_keycloak_url': 'http://keycloak.url/auth', + 'auth_password': 'admin', + 'auth_realm': 'master', + 'auth_username': 'admin', + 'realm': 'master', + 'required_actions': [ + { + 'alias': 'test-provider-id', + 'name': 'Test provider ID', + 'providerId': 'test-provider-id', + }, + { + 'alias': 'test-provider-id', + 'name': 'Test provider ID (DUPLICATE ALIAS)', + 'providerId': 'test-provider-id', + }, + { + 'alias': 'test-provider-id', + 'name': 'Test provider ID (DIFFERENT PROVIDER ID)', + 'providerId': 'test-provider-id-diff', + }, + ], + 'state': 'present', + } + + return_value_required_actions = [ + [ + { + 'alias': 'CONFIGURE_TOTP', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Configure OTP', + 'priority': 10, + 'providerId': 'CONFIGURE_TOTP' + }, + { + 'alias': 'TERMS_AND_CONDITIONS', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Terms and conditions', + 'priority': 20, + 'providerId': 'TERMS_AND_CONDITIONS' + }, + { + 'alias': 'UPDATE_PASSWORD', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Update Password', + 'priority': 30, + 'providerId': 'UPDATE_PASSWORD' + }, + { + 'alias': 'UPDATE_PROFILE', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Update Profile', + 'priority': 40, + 'providerId': 'UPDATE_PROFILE' + }, + { + 'alias': 'VERIFY_EMAIL', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Verify Email', + 'priority': 50, + 'providerId': 'VERIFY_EMAIL' + }, + { + 'alias': 'delete_account', + 'config': {}, + 'defaultAction': False, + 'enabled': False, + 'name': 'Delete Account', + 'priority': 60, + 'providerId': 'delete_account' + }, + { + 'alias': 'webauthn-register', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Webauthn Register', + 'priority': 70, + 'providerId': 'webauthn-register' + }, + { + 'alias': 'webauthn-register-passwordless', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Webauthn Register Passwordless', + 'priority': 80, + 'providerId': 'webauthn-register-passwordless' + }, + { + 'alias': 'update_user_locale', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Update User Locale', + 'priority': 1000, + 'providerId': 'update_user_locale' + } + ], + ] + + changed = True + + set_module_args(module_args) + + # Run the module + with mock_good_connection(): + with patch_keycloak_api( + get_required_actions=return_value_required_actions, + ) as ( + mock_get_required_actions, + mock_register_required_action, + mock_update_required_action, + mock_delete_required_action, + ): + with self.assertRaises(AnsibleExitJson) as exec_info: + self.module.main() + + # Verify number of call on each mock + self.assertEqual(len(mock_get_required_actions.mock_calls), 1) + self.assertEqual(len(mock_update_required_action.mock_calls), 1) + self.assertEqual(len(mock_register_required_action.mock_calls), 1) + self.assertEqual(len(mock_delete_required_action.mock_calls), 0) + + # Verify that the module's changed status matches what is expected + self.assertIs(exec_info.exception.args[0]['changed'], changed) + + def test_register_required_action_idempotency(self): + """Register an already existing new authentication required action again.""" + + module_args = { + 'auth_client_id': 'admin-cli', + 'auth_keycloak_url': 'http://keycloak.url/auth', + 'auth_password': 'admin', + 'auth_realm': 'master', + 'auth_username': 'admin', + 'realm': 'master', + 'required_actions': [ + { + 'alias': 'test-provider-id', + 'name': 'Test provider ID', + 'providerId': 'test-provider-id', + }, + { + 'alias': 'test-provider-id', + 'name': 'Test provider ID (DUPLICATE ALIAS)', + 'providerId': 'test-provider-id', + }, + { + 'alias': 'test-provider-id', + 'name': 'Test provider ID (DIFFERENT PROVIDER ID)', + 'providerId': 'test-provider-id-diff', + }, + ], + 'state': 'present', + } + + return_value_required_actions = [ + [ + { + 'alias': 'CONFIGURE_TOTP', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Configure OTP', + 'priority': 10, + 'providerId': 'CONFIGURE_TOTP' + }, + { + 'alias': 'TERMS_AND_CONDITIONS', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Terms and conditions', + 'priority': 20, + 'providerId': 'TERMS_AND_CONDITIONS' + }, + { + 'alias': 'UPDATE_PASSWORD', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Update Password', + 'priority': 30, + 'providerId': 'UPDATE_PASSWORD' + }, + { + 'alias': 'UPDATE_PROFILE', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Update Profile', + 'priority': 40, + 'providerId': 'UPDATE_PROFILE' + }, + { + 'alias': 'VERIFY_EMAIL', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Verify Email', + 'priority': 50, + 'providerId': 'VERIFY_EMAIL' + }, + { + 'alias': 'delete_account', + 'config': {}, + 'defaultAction': False, + 'enabled': False, + 'name': 'Delete Account', + 'priority': 60, + 'providerId': 'delete_account' + }, + { + 'alias': 'webauthn-register', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Webauthn Register', + 'priority': 70, + 'providerId': 'webauthn-register' + }, + { + 'alias': 'webauthn-register-passwordless', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Webauthn Register Passwordless', + 'priority': 80, + 'providerId': 'webauthn-register-passwordless' + }, + { + 'alias': 'test-provider-id', + 'config': {}, + 'defaultAction': False, + 'enabled': False, + 'name': 'Test provider ID', + 'priority': 90, + 'providerId': 'test-provider-id' + }, + { + 'alias': 'update_user_locale', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Update User Locale', + 'priority': 1000, + 'providerId': 'update_user_locale' + } + ], + ] + + changed = False + + set_module_args(module_args) + + # Run the module + with mock_good_connection(): + with patch_keycloak_api( + get_required_actions=return_value_required_actions, + ) as ( + mock_get_required_actions, + mock_register_required_action, + mock_update_required_action, + mock_delete_required_action, + ): + with self.assertRaises(AnsibleExitJson) as exec_info: + self.module.main() + + # Verify number of call on each mock + self.assertEqual(len(mock_get_required_actions.mock_calls), 1) + self.assertEqual(len(mock_update_required_action.mock_calls), 0) + self.assertEqual(len(mock_register_required_action.mock_calls), 0) + self.assertEqual(len(mock_delete_required_action.mock_calls), 0) + + # Verify that the module's changed status matches what is expected + self.assertIs(exec_info.exception.args[0]['changed'], changed) + + def test_update_required_actions(self): + """Update an authentication required action.""" + + module_args = { + 'auth_client_id': 'admin-cli', + 'auth_keycloak_url': 'http://keycloak.url/auth', + 'auth_password': 'admin', + 'auth_realm': 'master', + 'auth_username': 'admin', + 'realm': 'master', + 'required_actions': [ + { + 'alias': 'test-provider-id', + 'name': 'Test provider ID UPDATED', + 'providerId': 'test-provider-id', + }, + { + 'alias': 'test-provider-id', + 'name': 'Test provider ID UPDATED (DUPLICATE ALIAS)', + 'providerId': 'test-provider-id', + }, + { + 'alias': 'test-provider-id', + 'name': 'Test provider ID UPDATED (DIFFERENT PROVIDER ID)', + 'providerId': 'test-provider-id-diff', + }, + ], + 'state': 'present', + } + + return_value_required_actions = [ + [ + { + 'alias': 'CONFIGURE_TOTP', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Configure OTP', + 'priority': 10, + 'providerId': 'CONFIGURE_TOTP' + }, + { + 'alias': 'TERMS_AND_CONDITIONS', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Terms and conditions', + 'priority': 20, + 'providerId': 'TERMS_AND_CONDITIONS' + }, + { + 'alias': 'UPDATE_PASSWORD', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Update Password', + 'priority': 30, + 'providerId': 'UPDATE_PASSWORD' + }, + { + 'alias': 'UPDATE_PROFILE', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Update Profile', + 'priority': 40, + 'providerId': 'UPDATE_PROFILE' + }, + { + 'alias': 'VERIFY_EMAIL', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Verify Email', + 'priority': 50, + 'providerId': 'VERIFY_EMAIL' + }, + { + 'alias': 'delete_account', + 'config': {}, + 'defaultAction': False, + 'enabled': False, + 'name': 'Delete Account', + 'priority': 60, + 'providerId': 'delete_account' + }, + { + 'alias': 'webauthn-register', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Webauthn Register', + 'priority': 70, + 'providerId': 'webauthn-register' + }, + { + 'alias': 'webauthn-register-passwordless', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Webauthn Register Passwordless', + 'priority': 80, + 'providerId': 'webauthn-register-passwordless' + }, + { + 'alias': 'test-provider-id', + 'config': {}, + 'defaultAction': False, + 'enabled': False, + 'name': 'Test provider ID', + 'priority': 90, + 'providerId': 'test-provider-id' + }, + { + 'alias': 'update_user_locale', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Update User Locale', + 'priority': 1000, + 'providerId': 'update_user_locale' + } + ], + ] + + changed = True + + set_module_args(module_args) + + # Run the module + with mock_good_connection(): + with patch_keycloak_api( + get_required_actions=return_value_required_actions, + ) as ( + mock_get_required_actions, + mock_register_required_action, + mock_update_required_action, + mock_delete_required_action, + ): + with self.assertRaises(AnsibleExitJson) as exec_info: + self.module.main() + + # Verify number of call on each mock + self.assertEqual(len(mock_get_required_actions.mock_calls), 1) + self.assertEqual(len(mock_update_required_action.mock_calls), 1) + self.assertEqual(len(mock_register_required_action.mock_calls), 0) + self.assertEqual(len(mock_delete_required_action.mock_calls), 0) + + # Verify that the module's changed status matches what is expected + self.assertIs(exec_info.exception.args[0]['changed'], changed) + + def test_delete_required_action(self): + """Delete a registered authentication required action.""" + + module_args = { + 'auth_client_id': 'admin-cli', + 'auth_keycloak_url': 'http://keycloak.url/auth', + 'auth_password': 'admin', + 'auth_realm': 'master', + 'auth_username': 'admin', + 'realm': 'master', + 'required_actions': [ + { + 'alias': 'test-provider-id', + }, + ], + 'state': 'absent', + } + + return_value_required_actions = [ + [ + { + 'alias': 'CONFIGURE_TOTP', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Configure OTP', + 'priority': 10, + 'providerId': 'CONFIGURE_TOTP' + }, + { + 'alias': 'TERMS_AND_CONDITIONS', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Terms and conditions', + 'priority': 20, + 'providerId': 'TERMS_AND_CONDITIONS' + }, + { + 'alias': 'UPDATE_PASSWORD', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Update Password', + 'priority': 30, + 'providerId': 'UPDATE_PASSWORD' + }, + { + 'alias': 'UPDATE_PROFILE', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Update Profile', + 'priority': 40, + 'providerId': 'UPDATE_PROFILE' + }, + { + 'alias': 'VERIFY_EMAIL', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Verify Email', + 'priority': 50, + 'providerId': 'VERIFY_EMAIL' + }, + { + 'alias': 'delete_account', + 'config': {}, + 'defaultAction': False, + 'enabled': False, + 'name': 'Delete Account', + 'priority': 60, + 'providerId': 'delete_account' + }, + { + 'alias': 'webauthn-register', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Webauthn Register', + 'priority': 70, + 'providerId': 'webauthn-register' + }, + { + 'alias': 'webauthn-register-passwordless', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Webauthn Register Passwordless', + 'priority': 80, + 'providerId': 'webauthn-register-passwordless' + }, + { + 'alias': 'test-provider-id', + 'config': {}, + 'defaultAction': False, + 'enabled': False, + 'name': 'Test provider ID', + 'priority': 90, + 'providerId': 'test-provider-id' + }, + { + 'alias': 'update_user_locale', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Update User Locale', + 'priority': 1000, + 'providerId': 'update_user_locale' + } + ], + ] + + changed = True + + set_module_args(module_args) + + # Run the module + with mock_good_connection(): + with patch_keycloak_api( + get_required_actions=return_value_required_actions, + ) as ( + mock_get_required_actions, + mock_register_required_action, + mock_update_required_action, + mock_delete_required_action, + ): + with self.assertRaises(AnsibleExitJson) as exec_info: + self.module.main() + + # Verify number of call on each mock + self.assertEqual(len(mock_get_required_actions.mock_calls), 1) + self.assertEqual(len(mock_update_required_action.mock_calls), 0) + self.assertEqual(len(mock_register_required_action.mock_calls), 0) + self.assertEqual(len(mock_delete_required_action.mock_calls), 1) + + # Verify that the module's changed status matches what is expected + self.assertIs(exec_info.exception.args[0]['changed'], changed) + + def test_delete_required_action_idempotency(self): + """Delete an already deleted authentication required action.""" + + module_args = { + 'auth_client_id': 'admin-cli', + 'auth_keycloak_url': 'http://keycloak.url/auth', + 'auth_password': 'admin', + 'auth_realm': 'master', + 'auth_username': 'admin', + 'realm': 'master', + 'required_actions': [ + { + 'alias': 'test-provider-id', + }, + ], + 'state': 'absent', + } + + return_value_required_actions = [ + [ + { + 'alias': 'CONFIGURE_TOTP', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Configure OTP', + 'priority': 10, + 'providerId': 'CONFIGURE_TOTP' + }, + { + 'alias': 'TERMS_AND_CONDITIONS', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Terms and conditions', + 'priority': 20, + 'providerId': 'TERMS_AND_CONDITIONS' + }, + { + 'alias': 'UPDATE_PASSWORD', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Update Password', + 'priority': 30, + 'providerId': 'UPDATE_PASSWORD' + }, + { + 'alias': 'UPDATE_PROFILE', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Update Profile', + 'priority': 40, + 'providerId': 'UPDATE_PROFILE' + }, + { + 'alias': 'VERIFY_EMAIL', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Verify Email', + 'priority': 50, + 'providerId': 'VERIFY_EMAIL' + }, + { + 'alias': 'delete_account', + 'config': {}, + 'defaultAction': False, + 'enabled': False, + 'name': 'Delete Account', + 'priority': 60, + 'providerId': 'delete_account' + }, + { + 'alias': 'webauthn-register', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Webauthn Register', + 'priority': 70, + 'providerId': 'webauthn-register' + }, + { + 'alias': 'webauthn-register-passwordless', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Webauthn Register Passwordless', + 'priority': 80, + 'providerId': 'webauthn-register-passwordless' + }, + { + 'alias': 'update_user_locale', + 'config': {}, + 'defaultAction': False, + 'enabled': True, + 'name': 'Update User Locale', + 'priority': 1000, + 'providerId': 'update_user_locale' + } + ], + ] + + changed = False + + set_module_args(module_args) + + # Run the module + with mock_good_connection(): + with patch_keycloak_api( + get_required_actions=return_value_required_actions, + ) as ( + mock_get_required_actions, + mock_register_required_action, + mock_update_required_action, + mock_delete_required_action, + ): + with self.assertRaises(AnsibleExitJson) as exec_info: + self.module.main() + + # Verify number of call on each mock + self.assertEqual(len(mock_get_required_actions.mock_calls), 1) + self.assertEqual(len(mock_update_required_action.mock_calls), 0) + self.assertEqual(len(mock_register_required_action.mock_calls), 0) + self.assertEqual(len(mock_delete_required_action.mock_calls), 0) + + # Verify that the module's changed status matches what is expected + self.assertIs(exec_info.exception.args[0]['changed'], changed) + + +if __name__ == '__main__': + unittest.main() diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_keycloak_client_rolemapping.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_keycloak_client_rolemapping.py index 58c8b9548..359e6304e 100644 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_keycloak_client_rolemapping.py +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_keycloak_client_rolemapping.py @@ -120,6 +120,11 @@ class TestKeycloakRealm(ModuleTestCase): 'state': 'present', 'client_id': 'test_client', 'group_name': 'test_group', + 'parents': [ + { + 'name': 'parent_group' + } + ], 'roles': [ { 'name': 'test_role1', @@ -139,7 +144,7 @@ class TestKeycloakRealm(ModuleTestCase): "clientRoles": "{}", "id": "92f2400e-0ecb-4185-8950-12dcef616c2b", "name": "test_group", - "path": "/test_group", + "path": "/parent_group/test_group", "realmRoles": "[]", "subGroups": "[]" }] diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_keycloak_role.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_keycloak_role.py index c48c9771a..cc2f6e716 100644 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_keycloak_role.py +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_keycloak_role.py @@ -21,7 +21,9 @@ from ansible.module_utils.six import StringIO @contextmanager -def patch_keycloak_api(get_realm_role, create_realm_role=None, update_realm_role=None, delete_realm_role=None): +def patch_keycloak_api(get_realm_role=None, create_realm_role=None, update_realm_role=None, delete_realm_role=None, + get_client_role=None, create_client_role=None, update_client_role=None, delete_client_role=None, + get_client_by_id=None, get_role_composites=None): """Mock context manager for patching the methods in PwPolicyIPAClient that contact the IPA server Patches the `login` and `_post_json` methods @@ -41,7 +43,15 @@ def patch_keycloak_api(get_realm_role, create_realm_role=None, update_realm_role with patch.object(obj, 'create_realm_role', side_effect=create_realm_role) as mock_create_realm_role: with patch.object(obj, 'update_realm_role', side_effect=update_realm_role) as mock_update_realm_role: with patch.object(obj, 'delete_realm_role', side_effect=delete_realm_role) as mock_delete_realm_role: - yield mock_get_realm_role, mock_create_realm_role, mock_update_realm_role, mock_delete_realm_role + with patch.object(obj, 'get_client_role', side_effect=get_client_role) as mock_get_client_role: + with patch.object(obj, 'create_client_role', side_effect=create_client_role) as mock_create_client_role: + with patch.object(obj, 'update_client_role', side_effect=update_client_role) as mock_update_client_role: + with patch.object(obj, 'delete_client_role', side_effect=delete_client_role) as mock_delete_client_role: + with patch.object(obj, 'get_client_by_id', side_effect=get_client_by_id) as mock_get_client_by_id: + with patch.object(obj, 'get_role_composites', side_effect=get_role_composites) as mock_get_role_composites: + yield mock_get_realm_role, mock_create_realm_role, mock_update_realm_role, mock_delete_realm_role, \ + mock_get_client_role, mock_create_client_role, mock_update_client_role, mock_delete_client_role, \ + mock_get_client_by_id, mock_get_role_composites def get_response(object_with_future_response, method, get_id_call_count): @@ -125,7 +135,9 @@ class TestKeycloakRealmRole(ModuleTestCase): with mock_good_connection(): with patch_keycloak_api(get_realm_role=return_value_absent, create_realm_role=return_value_created) \ - as (mock_get_realm_role, mock_create_realm_role, mock_update_realm_role, mock_delete_realm_role): + as (mock_get_realm_role, mock_create_realm_role, mock_update_realm_role, mock_delete_realm_role, + mock_get_client_role, mock_create_client_role, mock_update_client_role, mock_delete_client_role, + mock_get_client_by_client_id, mock_get_role_composites): with self.assertRaises(AnsibleExitJson) as exec_info: self.module.main() @@ -179,7 +191,9 @@ class TestKeycloakRealmRole(ModuleTestCase): with mock_good_connection(): with patch_keycloak_api(get_realm_role=return_value_present, update_realm_role=return_value_updated) \ - as (mock_get_realm_role, mock_create_realm_role, mock_update_realm_role, mock_delete_realm_role): + as (mock_get_realm_role, mock_create_realm_role, mock_update_realm_role, mock_delete_realm_role, + mock_get_client_role, mock_create_client_role, mock_update_client_role, mock_delete_client_role, + mock_get_client_by_client_id, mock_get_role_composites): with self.assertRaises(AnsibleExitJson) as exec_info: self.module.main() @@ -233,7 +247,9 @@ class TestKeycloakRealmRole(ModuleTestCase): with mock_good_connection(): with patch_keycloak_api(get_realm_role=return_value_present, update_realm_role=return_value_updated) \ - as (mock_get_realm_role, mock_create_realm_role, mock_update_realm_role, mock_delete_realm_role): + as (mock_get_realm_role, mock_create_realm_role, mock_update_realm_role, mock_delete_realm_role, + mock_get_client_role, mock_create_client_role, mock_update_client_role, mock_delete_client_role, + mock_get_client_by_client_id, mock_get_role_composites): with self.assertRaises(AnsibleExitJson) as exec_info: self.module.main() @@ -244,6 +260,140 @@ class TestKeycloakRealmRole(ModuleTestCase): # Verify that the module's changed status matches what is expected self.assertIs(exec_info.exception.args[0]['changed'], changed) + def test_create_with_composites_when_present_no_change(self): + """Update without change a realm role""" + + module_args = { + 'auth_keycloak_url': 'http://keycloak.url/auth', + 'auth_password': 'admin', + 'auth_realm': 'master', + 'auth_username': 'admin', + 'auth_client_id': 'admin-cli', + 'validate_certs': True, + 'realm': 'realm-name', + 'name': 'role-name', + 'description': 'role-description', + 'composite': True, + 'composites': [ + { + 'client_id': 'client_1', + 'name': 'client-role1' + }, + { + 'name': 'realm-role-1' + } + ] + + } + return_value_present = [ + { + "attributes": {}, + "clientRole": False, + "composite": True, + "containerId": "realm-name", + "description": "role-description", + "id": "90f1cdb6-be88-496e-89c6-da1fb6bc6966", + "name": "role-name", + }, + { + "attributes": {}, + "clientRole": False, + "composite": True, + "containerId": "realm-name", + "description": "role-description", + "id": "90f1cdb6-be88-496e-89c6-da1fb6bc6966", + "name": "role-name", + } + ] + return_value_updated = [None] + return_get_role_composites = [ + [ + { + 'clientRole': True, + 'containerId': 'c4367fac-f427-11ed-8e2f-aff070d20f0e', + 'name': 'client-role1' + }, + { + 'clientRole': False, + 'containerId': 'realm-name', + 'name': 'realm-role-1' + } + ] + ] + return_get_client_by_client_id = [ + { + "id": "de152444-f126-4a7a-8273-4ee1544133ad", + "clientId": "client_1", + "name": "client_1", + "description": "client_1", + "surrogateAuthRequired": False, + "enabled": True, + "alwaysDisplayInConsole": False, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "http://localhost:8080/*", + ], + "webOrigins": [ + "*" + ], + "notBefore": 0, + "bearerOnly": False, + "consentRequired": False, + "standardFlowEnabled": True, + "implicitFlowEnabled": False, + "directAccessGrantsEnabled": False, + "serviceAccountsEnabled": False, + "publicClient": False, + "frontchannelLogout": False, + "protocol": "openid-connect", + "attributes": { + "backchannel.logout.session.required": "true", + "backchannel.logout.revoke.offline.tokens": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": True, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ] + + changed = False + + set_module_args(module_args) + + # Run the module + + with mock_good_connection(): + with patch_keycloak_api(get_realm_role=return_value_present, update_realm_role=return_value_updated, + get_client_by_id=return_get_client_by_client_id, + get_role_composites=return_get_role_composites) \ + as (mock_get_realm_role, mock_create_realm_role, mock_update_realm_role, mock_delete_realm_role, + mock_get_client_role, mock_create_client_role, mock_update_client_role, mock_delete_client_role, + mock_get_client_by_client_id, mock_get_role_composites): + with self.assertRaises(AnsibleExitJson) as exec_info: + self.module.main() + + self.assertEqual(len(mock_get_realm_role.mock_calls), 1) + self.assertEqual(len(mock_create_realm_role.mock_calls), 0) + self.assertEqual(len(mock_update_realm_role.mock_calls), 0) + self.assertEqual(len(mock_get_client_by_client_id.mock_calls), 1) + self.assertEqual(len(mock_get_role_composites.mock_calls), 1) + + # Verify that the module's changed status matches what is expected + self.assertIs(exec_info.exception.args[0]['changed'], changed) + def test_delete_when_absent(self): """Remove an absent realm role""" @@ -268,7 +418,9 @@ class TestKeycloakRealmRole(ModuleTestCase): with mock_good_connection(): with patch_keycloak_api(get_realm_role=return_value_absent, delete_realm_role=return_value_deleted) \ - as (mock_get_realm_role, mock_create_realm_role, mock_update_realm_role, mock_delete_realm_role): + as (mock_get_realm_role, mock_create_realm_role, mock_update_realm_role, mock_delete_realm_role, + mock_get_client_role, mock_create_client_role, mock_update_client_role, mock_delete_client_role, + mock_get_client_by_client_id, mock_get_role_composites): with self.assertRaises(AnsibleExitJson) as exec_info: self.module.main() @@ -312,7 +464,9 @@ class TestKeycloakRealmRole(ModuleTestCase): with mock_good_connection(): with patch_keycloak_api(get_realm_role=return_value_absent, delete_realm_role=return_value_deleted) \ - as (mock_get_realm_role, mock_create_realm_role, mock_update_realm_role, mock_delete_realm_role): + as (mock_get_realm_role, mock_create_realm_role, mock_update_realm_role, mock_delete_realm_role, + mock_get_client_role, mock_create_client_role, mock_update_client_role, mock_delete_client_role, + mock_get_client_by_client_id, mock_get_role_composites): with self.assertRaises(AnsibleExitJson) as exec_info: self.module.main() @@ -323,5 +477,207 @@ class TestKeycloakRealmRole(ModuleTestCase): self.assertIs(exec_info.exception.args[0]['changed'], changed) +class TestKeycloakClientRole(ModuleTestCase): + def setUp(self): + super(TestKeycloakClientRole, self).setUp() + self.module = keycloak_role + + def test_create_client_role_with_composites_when_absent(self): + """Update with change a realm role""" + + module_args = { + 'auth_keycloak_url': 'http://keycloak.url/auth', + 'auth_password': 'admin', + 'auth_realm': 'master', + 'auth_username': 'admin', + 'auth_client_id': 'admin-cli', + 'validate_certs': True, + 'realm': 'realm-name', + 'client_id': 'client-name', + 'name': 'role-name', + 'description': 'role-description', + 'composite': True, + 'composites': [ + { + 'client_id': 'client_1', + 'name': 'client-role1' + }, + { + 'name': 'realm-role-1' + } + ] + } + return_get_client_role = [ + None, + { + "attributes": {}, + "clientRole": True, + "composite": True, + "composites": [ + { + 'client': { + 'client1': ['client-role1'] + } + }, + { + 'realm': ['realm-role-1'] + } + ], + "containerId": "9ae25ec2-f40a-11ed-9261-b3bacf720f69", + "description": "role-description", + "id": "90f1cdb6-be88-496e-89c6-da1fb6bc6966", + "name": "role-name", + } + ] + changed = True + + set_module_args(module_args) + + # Run the module + + with mock_good_connection(): + with patch_keycloak_api(get_client_role=return_get_client_role) \ + as (mock_get_realm_role, mock_create_realm_role, mock_update_realm_role, mock_delete_realm_role, + mock_get_client_role, mock_create_client_role, mock_update_client_role, mock_delete_client_role, + mock_get_client_by_client_id, mock_get_role_composites): + with self.assertRaises(AnsibleExitJson) as exec_info: + self.module.main() + + self.assertEqual(len(mock_get_realm_role.mock_calls), 0) + self.assertEqual(len(mock_create_realm_role.mock_calls), 0) + self.assertEqual(len(mock_update_realm_role.mock_calls), 0) + self.assertEqual(len(mock_get_client_role.mock_calls), 2) + self.assertEqual(len(mock_create_client_role.mock_calls), 1) + self.assertEqual(len(mock_update_client_role.mock_calls), 0) + + # Verify that the module's changed status matches what is expected + self.assertIs(exec_info.exception.args[0]['changed'], changed) + + def test_create_client_role_with_composites_when_present_no_change(self): + """Update with change a realm role""" + + module_args = { + 'auth_keycloak_url': 'http://keycloak.url/auth', + 'auth_password': 'admin', + 'auth_realm': 'master', + 'auth_username': 'admin', + 'auth_client_id': 'admin-cli', + 'validate_certs': True, + 'realm': 'realm-name', + 'client_id': 'client-name', + 'name': 'role-name', + 'description': 'role-description', + 'composite': True, + 'composites': [ + { + 'client_id': 'client_1', + 'name': 'client-role1' + }, + { + 'name': 'realm-role-1' + } + ] + } + return_get_client_role = [ + { + "attributes": {}, + "clientRole": True, + "composite": True, + "containerId": "9ae25ec2-f40a-11ed-9261-b3bacf720f69", + "description": "role-description", + "id": "90f1cdb6-be88-496e-89c6-da1fb6bc6966", + "name": "role-name", + } + ] + return_get_role_composites = [ + [ + { + 'clientRole': True, + 'containerId': 'c4367fac-f427-11ed-8e2f-aff070d20f0e', + 'name': 'client-role1' + }, + { + 'clientRole': False, + 'containerId': 'realm-name', + 'name': 'realm-role-1' + } + ] + ] + return_get_client_by_client_id = [ + { + "id": "de152444-f126-4a7a-8273-4ee1544133ad", + "clientId": "client_1", + "name": "client_1", + "description": "client_1", + "surrogateAuthRequired": False, + "enabled": True, + "alwaysDisplayInConsole": False, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "http://localhost:8080/*", + ], + "webOrigins": [ + "*" + ], + "notBefore": 0, + "bearerOnly": False, + "consentRequired": False, + "standardFlowEnabled": True, + "implicitFlowEnabled": False, + "directAccessGrantsEnabled": False, + "serviceAccountsEnabled": False, + "publicClient": False, + "frontchannelLogout": False, + "protocol": "openid-connect", + "attributes": { + "backchannel.logout.session.required": "true", + "backchannel.logout.revoke.offline.tokens": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": True, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ] + changed = False + + set_module_args(module_args) + + # Run the module + + with mock_good_connection(): + with patch_keycloak_api(get_client_role=return_get_client_role, get_client_by_id=return_get_client_by_client_id, + get_role_composites=return_get_role_composites) \ + as (mock_get_realm_role, mock_create_realm_role, mock_update_realm_role, mock_delete_realm_role, + mock_get_client_role, mock_create_client_role, mock_update_client_role, mock_delete_client_role, + mock_get_client_by_client_id, mock_get_role_composites): + with self.assertRaises(AnsibleExitJson) as exec_info: + self.module.main() + + self.assertEqual(len(mock_get_realm_role.mock_calls), 0) + self.assertEqual(len(mock_create_realm_role.mock_calls), 0) + self.assertEqual(len(mock_update_realm_role.mock_calls), 0) + self.assertEqual(len(mock_get_client_role.mock_calls), 1) + self.assertEqual(len(mock_create_client_role.mock_calls), 0) + self.assertEqual(len(mock_update_client_role.mock_calls), 0) + self.assertEqual(len(mock_get_client_by_client_id.mock_calls), 1) + self.assertEqual(len(mock_get_role_composites.mock_calls), 1) + + # Verify that the module's changed status matches what is expected + self.assertIs(exec_info.exception.args[0]['changed'], changed) + + if __name__ == '__main__': unittest.main() diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_keycloak_user.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_keycloak_user.py new file mode 100644 index 000000000..26bc33d82 --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_keycloak_user.py @@ -0,0 +1,354 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2021, 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 contextlib import contextmanager + +from ansible_collections.community.general.tests.unit.compat import unittest +from ansible_collections.community.general.tests.unit.compat.mock import patch +from ansible_collections.community.general.tests.unit.plugins.modules.utils import AnsibleExitJson, ModuleTestCase, set_module_args + +from ansible_collections.community.general.plugins.modules import keycloak_user + +from itertools import count + +from ansible.module_utils.six import StringIO + + +@contextmanager +def patch_keycloak_api(get_user_by_username=None, + create_user=None, + update_user_groups_membership=None, + get_user_groups=None, + delete_user=None, + update_user=None): + """Mock context manager for patching the methods in KeycloakAPI that contact the Keycloak server + + Patches the `get_user_by_username` and `create_user` methods + + """ + + obj = keycloak_user.KeycloakAPI + with patch.object(obj, 'get_user_by_username', side_effect=get_user_by_username) as mock_get_user_by_username: + with patch.object(obj, 'create_user', side_effect=create_user) as mock_create_user: + with patch.object(obj, 'update_user_groups_membership', side_effect=update_user_groups_membership) as mock_update_user_groups_membership: + with patch.object(obj, 'get_user_groups', side_effect=get_user_groups) as mock_get_user_groups: + with patch.object(obj, 'delete_user', side_effect=delete_user) as mock_delete_user: + with patch.object(obj, 'update_user', side_effect=update_user) as mock_update_user: + yield mock_get_user_by_username, mock_create_user, mock_update_user_groups_membership, \ + mock_get_user_groups, mock_delete_user, mock_update_user + + +def get_response(object_with_future_response, method, get_id_call_count): + if callable(object_with_future_response): + return object_with_future_response() + if isinstance(object_with_future_response, dict): + return get_response( + object_with_future_response[method], method, get_id_call_count) + if isinstance(object_with_future_response, list): + call_number = next(get_id_call_count) + return get_response( + object_with_future_response[call_number], method, get_id_call_count) + return object_with_future_response + + +def build_mocked_request(get_id_user_count, response_dict): + def _mocked_requests(*args, **kwargs): + url = args[0] + method = kwargs['method'] + future_response = response_dict.get(url, None) + return get_response(future_response, method, get_id_user_count) + + return _mocked_requests + + +def create_wrapper(text_as_string): + """Allow to mock many times a call to one address. + Without this function, the StringIO is empty for the second call. + """ + + def _create_wrapper(): + return StringIO(text_as_string) + + return _create_wrapper + + +def mock_good_connection(): + token_response = { + 'http://keycloak.url/auth/realms/master/protocol/openid-connect/token': create_wrapper( + '{"access_token": "alongtoken"}'), } + return patch( + 'ansible_collections.community.general.plugins.module_utils.identity.keycloak.keycloak.open_url', + side_effect=build_mocked_request(count(), token_response), + autospec=True + ) + + +class TestKeycloakUser(ModuleTestCase): + def setUp(self): + super(TestKeycloakUser, self).setUp() + self.module = keycloak_user + + def test_add_new_user(self): + """Add a new user""" + + module_args = { + 'auth_keycloak_url': 'https: // auth.example.com / auth', + 'token': '{{ access_token }}', + 'state': 'present', + 'realm': 'master', + 'username': 'test', + 'groups': [] + } + return_value_get_user_by_username = [None] + return_value_update_user_groups_membership = [False] + return_get_user_groups = [[]] + return_create_user = [{'id': '123eqwdawer24qwdqw4'}] + return_delete_user = None + return_update_user = None + changed = True + + set_module_args(module_args) + + # Run the module + + with mock_good_connection(): + with patch_keycloak_api(get_user_by_username=return_value_get_user_by_username, + create_user=return_create_user, + update_user_groups_membership=return_value_update_user_groups_membership, + get_user_groups=return_get_user_groups, + update_user=return_update_user, + delete_user=return_delete_user) \ + as (mock_get_user_by_username, + mock_create_user, + mock_update_user_groups_membership, + mock_get_user_groups, + mock_delete_user, + mock_update_user): + with self.assertRaises(AnsibleExitJson) as exec_info: + self.module.main() + + self.assertEqual(mock_get_user_by_username.call_count, 1) + self.assertEqual(mock_create_user.call_count, 1) + self.assertEqual(mock_update_user_groups_membership.call_count, 1) + self.assertEqual(mock_get_user_groups.call_count, 1) + self.assertEqual(mock_update_user.call_count, 0) + self.assertEqual(mock_delete_user.call_count, 0) + + # Verify that the module's changed status matches what is expected + self.assertIs(exec_info.exception.args[0]['changed'], changed) + + def test_add_exiting_user_no_change(self): + """Add a new user""" + + module_args = { + 'auth_keycloak_url': 'https: // auth.example.com / auth', + 'token': '{{ access_token }}', + 'state': 'present', + 'realm': 'master', + 'username': 'test', + 'groups': [] + } + return_value_get_user_by_username = [ + { + 'id': '123eqwdawer24qwdqw4', + 'username': 'test', + 'groups': [], + 'enabled': True, + 'emailVerified': False, + 'disableableCredentialTypes': [], + 'requiredActions': [], + 'credentials': [], + 'federatedIdentities': [], + 'clientConsents': [] + } + ] + return_value_update_user_groups_membership = [False] + return_get_user_groups = [[]] + return_create_user = None + return_delete_user = None + return_update_user = None + changed = False + + set_module_args(module_args) + + # Run the module + + with mock_good_connection(): + with patch_keycloak_api(get_user_by_username=return_value_get_user_by_username, + create_user=return_create_user, + update_user_groups_membership=return_value_update_user_groups_membership, + get_user_groups=return_get_user_groups, + update_user=return_update_user, + delete_user=return_delete_user) \ + as (mock_get_user_by_username, + mock_create_user, + mock_update_user_groups_membership, + mock_get_user_groups, + mock_delete_user, + mock_update_user): + with self.assertRaises(AnsibleExitJson) as exec_info: + self.module.main() + + self.assertEqual(mock_get_user_by_username.call_count, 1) + self.assertEqual(mock_create_user.call_count, 0) + self.assertEqual(mock_update_user_groups_membership.call_count, 1) + self.assertEqual(mock_get_user_groups.call_count, 1) + self.assertEqual(mock_update_user.call_count, 0) + self.assertEqual(mock_delete_user.call_count, 0) + + # Verify that the module's changed status matches what is expected + self.assertIs(exec_info.exception.args[0]['changed'], changed) + + def test_update_user_with_group_changes(self): + """Update groups for a user""" + + module_args = { + 'auth_keycloak_url': 'https: // auth.example.com / auth', + 'token': '{{ access_token }}', + 'state': 'present', + 'realm': 'master', + 'username': 'test', + 'first_name': 'test', + 'last_name': 'user', + 'groups': [{ + 'name': 'group1', + 'state': 'present' + }] + } + return_value_get_user_by_username = [ + { + 'id': '123eqwdawer24qwdqw4', + 'username': 'test', + 'groups': [], + 'enabled': True, + 'emailVerified': False, + 'disableableCredentialTypes': [], + 'requiredActions': [], + 'credentials': [], + 'federatedIdentities': [], + 'clientConsents': [] + } + ] + return_value_update_user_groups_membership = [True] + return_get_user_groups = [['group1']] + return_create_user = None + return_delete_user = None + return_update_user = [ + { + 'id': '123eqwdawer24qwdqw4', + 'username': 'test', + 'first_name': 'test', + 'last_name': 'user', + 'enabled': True, + 'emailVerified': False, + 'disableableCredentialTypes': [], + 'requiredActions': [], + 'credentials': [], + 'federatedIdentities': [], + 'clientConsents': [] + } + ] + changed = True + + set_module_args(module_args) + + # Run the module + + with mock_good_connection(): + with patch_keycloak_api(get_user_by_username=return_value_get_user_by_username, + create_user=return_create_user, + update_user_groups_membership=return_value_update_user_groups_membership, + get_user_groups=return_get_user_groups, + update_user=return_update_user, + delete_user=return_delete_user) \ + as (mock_get_user_by_username, + mock_create_user, + mock_update_user_groups_membership, + mock_get_user_groups, + mock_delete_user, + mock_update_user): + with self.assertRaises(AnsibleExitJson) as exec_info: + self.module.main() + + self.assertEqual(mock_get_user_by_username.call_count, 1) + self.assertEqual(mock_create_user.call_count, 0) + self.assertEqual(mock_update_user_groups_membership.call_count, 1) + self.assertEqual(mock_get_user_groups.call_count, 1) + self.assertEqual(mock_update_user.call_count, 1) + self.assertEqual(mock_delete_user.call_count, 0) + + # Verify that the module's changed status matches what is expected + self.assertIs(exec_info.exception.args[0]['changed'], changed) + + def test_delete_user(self): + """Delete a user""" + + module_args = { + 'auth_keycloak_url': 'https: // auth.example.com / auth', + 'token': '{{ access_token }}', + 'state': 'absent', + 'realm': 'master', + 'username': 'test', + 'groups': [] + } + return_value_get_user_by_username = [ + { + 'id': '123eqwdawer24qwdqw4', + 'username': 'test', + 'groups': [], + 'enabled': True, + 'emailVerified': False, + 'disableableCredentialTypes': [], + 'requiredActions': [], + 'credentials': [], + 'federatedIdentities': [], + 'clientConsents': [] + } + ] + return_value_update_user_groups_membership = None + return_get_user_groups = None + return_create_user = None + return_delete_user = None + return_update_user = None + changed = True + + set_module_args(module_args) + + # Run the module + + with mock_good_connection(): + with patch_keycloak_api(get_user_by_username=return_value_get_user_by_username, + create_user=return_create_user, + update_user_groups_membership=return_value_update_user_groups_membership, + get_user_groups=return_get_user_groups, + update_user=return_update_user, + delete_user=return_delete_user) \ + as (mock_get_user_by_username, + mock_create_user, + mock_update_user_groups_membership, + mock_get_user_groups, + mock_delete_user, + mock_update_user): + with self.assertRaises(AnsibleExitJson) as exec_info: + self.module.main() + + self.assertEqual(mock_get_user_by_username.call_count, 1) + self.assertEqual(mock_create_user.call_count, 0) + self.assertEqual(mock_update_user_groups_membership.call_count, 0) + self.assertEqual(mock_get_user_groups.call_count, 0) + self.assertEqual(mock_update_user.call_count, 0) + self.assertEqual(mock_delete_user.call_count, 1) + + # Verify that the module's changed status matches what is expected + self.assertIs(exec_info.exception.args[0]['changed'], changed) + + +if __name__ == '__main__': + unittest.main() diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_keycloak_user_federation.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_keycloak_user_federation.py index 8d3dcaa23..523ef9f21 100644 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_keycloak_user_federation.py +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_keycloak_user_federation.py @@ -326,6 +326,7 @@ class TestKeycloakUserFederation(ModuleTestCase): 'connectionPooling': True, 'pagination': True, 'allowKerberosAuthentication': False, + 'krbPrincipalAttribute': 'krbPrincipalName', 'debug': False, 'useKerberosForPasswordAuthentication': False, }, @@ -374,6 +375,9 @@ class TestKeycloakUserFederation(ModuleTestCase): "enabled": [ "true" ], + "krbPrincipalAttribute": [ + "krb5PrincipalName" + ], "usernameLDAPAttribute": [ "uid" ], diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_lvg_rename.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_lvg_rename.py new file mode 100644 index 000000000..0f2fcb7fa --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_lvg_rename.py @@ -0,0 +1,160 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Contributors to the 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.plugins.modules import lvg_rename +from ansible_collections.community.general.tests.unit.compat.mock import patch +from ansible_collections.community.general.tests.unit.plugins.modules.utils import ( + AnsibleFailJson, AnsibleExitJson, ModuleTestCase, set_module_args) + + +VGS_OUTPUT = '''\ +vg_data_testhost1;XKZ5gn-YhWY-NlrT-QCFN-qmMG-VGT9-7uOmex +vg_sys_testhost2;xgy2SJ-YlYd-fde2-e3oG-zdXL-0xGf-ihqG2H +''' + + +class TestLvgRename(ModuleTestCase): + """Tests for lvg_rename internals""" + module = lvg_rename + module_path = 'ansible_collections.community.general.plugins.modules.lvg_rename' + + def setUp(self): + """Prepare mocks for module testing""" + super(TestLvgRename, self).setUp() + + self.mock_run_responses = {} + + patched_module_get_bin_path = patch('%s.AnsibleModule.get_bin_path' % (self.module_path)) + self.mock_module_get_bin_path = patched_module_get_bin_path.start() + self.mock_module_get_bin_path.return_value = '/mocpath' + self.addCleanup(patched_module_get_bin_path.stop) + + patched_module_run_command = patch('%s.AnsibleModule.run_command' % (self.module_path)) + self.mock_module_run_command = patched_module_run_command.start() + self.addCleanup(patched_module_run_command.stop) + + def test_vg_not_found_by_name(self): + """When the VG by the specified by vg name not found, the module should exit with error""" + failed = True + self.mock_module_run_command.side_effect = [(0, VGS_OUTPUT, '')] + expected_msg = 'Both current (vg_missing) and new (vg_data_testhost2) VG are missing.' + + module_args = { + 'vg': 'vg_missing', + 'vg_new': 'vg_data_testhost2', + } + set_module_args(args=module_args) + + with self.assertRaises(AnsibleFailJson) as result: + self.module.main() + + self.assertEqual(len(self.mock_module_run_command.mock_calls), 1) + self.assertIs(result.exception.args[0]['failed'], failed) + self.assertEqual(result.exception.args[0]['msg'], expected_msg) + + def test_vg_not_found_by_uuid(self): + """When the VG by the specified vg UUID not found, the module should exit with error""" + failed = True + self.mock_module_run_command.side_effect = [(0, VGS_OUTPUT, '')] + expected_msg = 'Both current (Yfj4YG-c8nI-z7w5-B7Fw-i2eM-HqlF-ApFVp0) and new (vg_data_testhost2) VG are missing.' + + module_args = { + 'vg': 'Yfj4YG-c8nI-z7w5-B7Fw-i2eM-HqlF-ApFVp0', + 'vg_new': 'vg_data_testhost2', + } + set_module_args(args=module_args) + + with self.assertRaises(AnsibleFailJson) as result: + self.module.main() + + self.assertEqual(len(self.mock_module_run_command.mock_calls), 1) + self.assertIs(result.exception.args[0]['failed'], failed) + self.assertEqual(result.exception.args[0]['msg'], expected_msg) + + def test_vg_and_vg_new_both_exists(self): + """When a VG found for both vg and vg_new options, the module should exit with error""" + failed = True + self.mock_module_run_command.side_effect = [(0, VGS_OUTPUT, '')] + expected_msg = 'The new VG name (vg_sys_testhost2) is already in use.' + + module_args = { + 'vg': 'vg_data_testhost1', + 'vg_new': 'vg_sys_testhost2', + } + set_module_args(args=module_args) + + with self.assertRaises(AnsibleFailJson) as result: + self.module.main() + + self.assertEqual(len(self.mock_module_run_command.mock_calls), 1) + self.assertIs(result.exception.args[0]['failed'], failed) + self.assertEqual(result.exception.args[0]['msg'], expected_msg) + + def test_vg_needs_renaming(self): + """When the VG found for vg option and there is no VG for vg_new option, + the module should call vgrename""" + changed = True + self.mock_module_run_command.side_effect = [ + (0, VGS_OUTPUT, ''), + (0, ' Volume group "vg_data_testhost1" successfully renamed to "vg_data_testhost2"', '') + ] + expected_msg = ' Volume group "vg_data_testhost1" successfully renamed to "vg_data_testhost2"' + + module_args = { + 'vg': '/dev/vg_data_testhost1', + 'vg_new': 'vg_data_testhost2', + } + set_module_args(args=module_args) + + with self.assertRaises(AnsibleExitJson) as result: + self.module.main() + + self.assertEqual(len(self.mock_module_run_command.mock_calls), 2) + self.assertIs(result.exception.args[0]['changed'], changed) + self.assertEqual(result.exception.args[0]['msg'], expected_msg) + + def test_vg_needs_renaming_in_check_mode(self): + """When running in check mode and the VG found for vg option and there is no VG for vg_new option, + the module should not call vgrename""" + changed = True + self.mock_module_run_command.side_effect = [(0, VGS_OUTPUT, '')] + expected_msg = 'Running in check mode. The module would rename VG /dev/vg_data_testhost1 to vg_data_testhost2.' + + module_args = { + 'vg': '/dev/vg_data_testhost1', + 'vg_new': 'vg_data_testhost2', + '_ansible_check_mode': True, + } + set_module_args(args=module_args) + + with self.assertRaises(AnsibleExitJson) as result: + self.module.main() + + self.assertEqual(len(self.mock_module_run_command.mock_calls), 1) + self.assertIs(result.exception.args[0]['changed'], changed) + self.assertEqual(result.exception.args[0]['msg'], expected_msg) + + def test_vg_needs_no_renaming(self): + """When the VG not found for vg option and the VG found for vg_new option, + the module should not call vgrename""" + changed = False + self.mock_module_run_command.side_effect = [(0, VGS_OUTPUT, '')] + expected_msg = 'The new VG (vg_data_testhost1) already exists, nothing to do.' + + module_args = { + 'vg': 'vg_data_testhostX', + 'vg_new': 'vg_data_testhost1', + } + set_module_args(args=module_args) + + with self.assertRaises(AnsibleExitJson) as result: + self.module.main() + + self.assertEqual(len(self.mock_module_run_command.mock_calls), 1) + self.assertIs(result.exception.args[0]['changed'], changed) + self.assertEqual(result.exception.args[0]['msg'], expected_msg) diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_modprobe.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_modprobe.py index 18695695a..2ad083151 100644 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_modprobe.py +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_modprobe.py @@ -152,7 +152,7 @@ class TestUnloadModule(ModuleTestCase): class TestModuleIsLoadedPersistently(ModuleTestCase): def setUp(self): if (sys.version_info[0] == 3 and sys.version_info[1] < 7) or (sys.version_info[0] == 2 and sys.version_info[1] < 7): - self.skipTest('open_mock doesnt support readline in earlier python versions') + self.skipTest("open_mock doesn't support readline in earlier python versions") super(TestModuleIsLoadedPersistently, self).setUp() @@ -230,7 +230,7 @@ class TestModuleIsLoadedPersistently(ModuleTestCase): class TestPermanentParams(ModuleTestCase): def setUp(self): if (sys.version_info[0] == 3 and sys.version_info[1] < 7) or (sys.version_info[0] == 2 and sys.version_info[1] < 7): - self.skipTest('open_mock doesnt support readline in earlier python versions') + self.skipTest("open_mock doesn't support readline in earlier python versions") super(TestPermanentParams, self).setUp() self.mock_get_bin_path = patch('ansible.module_utils.basic.AnsibleModule.get_bin_path') diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_nmcli.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_nmcli.py index efd8284a3..8c9c007ac 100644 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_nmcli.py +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_nmcli.py @@ -118,6 +118,12 @@ TESTCASE_CONNECTION = [ 'state': 'absent', '_ansible_check_mode': True, }, + { + 'type': 'loopback', + 'conn_name': 'non_existent_nw_device', + 'state': 'absent', + '_ansible_check_mode': True, + }, ] TESTCASE_GENERIC = [ @@ -262,6 +268,25 @@ ipv4.routes: { ip = 192.168.200.0/24, nh = 192.168.1. ipv4.route-metric: 10 """ +TESTCASE_ETHERNET_MOD_IPV4_INT_WITH_ROUTE_AND_METRIC_CLEAR = [ + { + 'type': 'ethernet', + 'conn_name': 'non_existent_nw_device', + 'routes4': [], + 'state': 'present', + '_ansible_check_mode': False, + '_ansible_diff': True, + }, + { + 'type': 'ethernet', + 'conn_name': 'non_existent_nw_device', + 'routes4_extended': [], + 'state': 'present', + '_ansible_check_mode': False, + '_ansible_diff': True, + }, +] + TESTCASE_ETHERNET_MOD_IPV6_INT_WITH_ROUTE_AND_METRIC = [ { 'type': 'ethernet', @@ -453,6 +478,38 @@ ipv6.ignore-auto-dns: no ipv6.ignore-auto-routes: no """ +TESTCASE_GENERIC_DNS4_OPTIONS = [ + { + 'type': 'generic', + 'conn_name': 'non_existent_nw_device', + 'ifname': 'generic_non_existant', + 'ip4': '10.10.10.10/24', + 'gw4': '10.10.10.1', + 'state': 'present', + 'dns4_options': [], + 'dns6_options': [], + '_ansible_check_mode': False, + } +] + +TESTCASE_GENERIC_DNS4_OPTIONS_SHOW_OUTPUT = """\ +connection.id: non_existent_nw_device +connection.interface-name: generic_non_existant +connection.autoconnect: yes +ipv4.method: manual +ipv4.addresses: 10.10.10.10/24 +ipv4.gateway: 10.10.10.1 +ipv4.ignore-auto-dns: no +ipv4.ignore-auto-routes: no +ipv4.never-default: no +ipv4.dns-options: -- +ipv4.may-fail: yes +ipv6.dns-options: -- +ipv6.method: auto +ipv6.ignore-auto-dns: no +ipv6.ignore-auto-routes: no +""" + TESTCASE_GENERIC_ZONE = [ { 'type': 'generic', @@ -569,6 +626,7 @@ TESTCASE_BRIDGE_SLAVE = [ 'type': 'bridge-slave', 'conn_name': 'non_existent_nw_device', 'ifname': 'br0_non_existant', + 'hairpin': True, 'path_cost': 100, 'state': 'present', '_ansible_check_mode': False, @@ -892,6 +950,28 @@ TESTCASE_ETHERNET_STATIC = [ } ] +TESTCASE_LOOPBACK = [ + { + 'type': 'loopback', + 'conn_name': 'lo', + 'ifname': 'lo', + 'ip4': '127.0.0.1/8', + 'state': 'present', + '_ansible_check_mode': False, + } +] + +TESTCASE_LOOPBACK_MODIFY = [ + { + 'type': 'loopback', + 'conn_name': 'lo', + 'ifname': 'lo', + 'ip4': ['127.0.0.1/8', '127.0.0.2/8'], + 'state': 'present', + '_ansible_check_mode': False, + } +] + TESTCASE_ETHERNET_STATIC_SHOW_OUTPUT = """\ connection.id: non_existent_nw_device connection.interface-name: ethernet_non_existant @@ -910,6 +990,21 @@ ipv6.ignore-auto-dns: no ipv6.ignore-auto-routes: no """ +TESTCASE_LOOPBACK_SHOW_OUTPUT = """\ +connection.id: lo +connection.interface-name: lo +connection.autoconnect: yes +ipv4.method: manual +ipv4.addresses: 127.0.0.1/8 +ipv4.ignore-auto-dns: no +ipv4.ignore-auto-routes: no +ipv4.never-default: no +ipv4.may-fail: yes +ipv6.method: manual +ipv6.ignore-auto-dns: no +ipv6.ignore-auto-routes: no +""" + TESTCASE_ETHERNET_STATIC_MULTIPLE_IP4_ADDRESSES = [ { 'type': 'ethernet', @@ -1393,7 +1488,8 @@ ipv4.may-fail: yes ipv6.method: auto ipv6.ignore-auto-dns: no ipv6.ignore-auto-routes: no -infiniband.transport-mode datagram +infiniband.mtu: auto +infiniband.transport-mode: datagram """ TESTCASE_INFINIBAND_STATIC_MODIFY_TRANSPORT_MODE = [ @@ -1514,6 +1610,13 @@ def mocked_generic_connection_dns_search_unchanged(mocker): @pytest.fixture +def mocked_generic_connection_dns_options_unchanged(mocker): + mocker_set(mocker, + connection_exists=True, + execute_return=(0, TESTCASE_GENERIC_DNS4_OPTIONS_SHOW_OUTPUT, "")) + + +@pytest.fixture def mocked_generic_connection_zone_unchanged(mocker): mocker_set(mocker, connection_exists=True, @@ -1672,6 +1775,17 @@ def mocked_ethernet_connection_with_ipv4_static_address_static_route_metric_modi @pytest.fixture +def mocked_ethernet_connection_with_ipv4_static_address_static_route_metric_clear(mocker): + mocker_set(mocker, + connection_exists=True, + execute_return=None, + execute_side_effect=( + (0, TESTCASE_ETHERNET_MOD_IPV4_INT_WITH_ROUTE_AND_METRIC_SHOW_OUTPUT, ""), + (0, "", ""), + )) + + +@pytest.fixture def mocked_ethernet_connection_with_ipv6_static_address_static_route_metric_modify(mocker): mocker_set(mocker, connection_exists=True, @@ -1875,6 +1989,24 @@ def mocked_generic_connection_diff_check(mocker): execute_return=(0, TESTCASE_GENERIC_SHOW_OUTPUT, "")) +@pytest.fixture +def mocked_loopback_connection_unchanged(mocker): + mocker_set(mocker, + connection_exists=True, + execute_return=(0, TESTCASE_LOOPBACK_SHOW_OUTPUT, "")) + + +@pytest.fixture +def mocked_loopback_connection_modify(mocker): + mocker_set(mocker, + connection_exists=True, + execute_return=None, + execute_side_effect=( + (0, TESTCASE_LOOPBACK_SHOW_OUTPUT, ""), + (0, "", ""), + )) + + @pytest.mark.parametrize('patch_ansible_module', TESTCASE_BOND, indirect=['patch_ansible_module']) def test_bond_connection_create(mocked_generic_connection_create, capfd): """ @@ -2066,6 +2198,62 @@ def test_generic_connection_dns_search_unchanged(mocked_generic_connection_dns_s assert not results['changed'] +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_GENERIC_DNS4_OPTIONS, indirect=['patch_ansible_module']) +def test_generic_connection_create_dns_options(mocked_generic_connection_create, capfd): + """ + Test : Generic connection created with dns options + """ + with pytest.raises(SystemExit): + nmcli.main() + + assert nmcli.Nmcli.execute_command.call_count == 1 + arg_list = nmcli.Nmcli.execute_command.call_args_list + args, kwargs = arg_list[0] + + assert 'ipv4.dns-options' in args[0] + assert 'ipv6.dns-options' in args[0] + + out, err = capfd.readouterr() + results = json.loads(out) + assert not results.get('failed') + assert results['changed'] + + +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_GENERIC_DNS4_OPTIONS, indirect=['patch_ansible_module']) +def test_generic_connection_modify_dns_options(mocked_generic_connection_create, capfd): + """ + Test : Generic connection modified with dns options + """ + with pytest.raises(SystemExit): + nmcli.main() + + assert nmcli.Nmcli.execute_command.call_count == 1 + arg_list = nmcli.Nmcli.execute_command.call_args_list + args, kwargs = arg_list[0] + + assert 'ipv4.dns-options' in args[0] + assert 'ipv6.dns-options' in args[0] + + out, err = capfd.readouterr() + results = json.loads(out) + assert not results.get('failed') + assert results['changed'] + + +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_GENERIC_DNS4_OPTIONS, indirect=['patch_ansible_module']) +def test_generic_connection_dns_options_unchanged(mocked_generic_connection_dns_options_unchanged, capfd): + """ + Test : Generic connection with dns options unchanged + """ + with pytest.raises(SystemExit): + nmcli.main() + + out, err = capfd.readouterr() + results = json.loads(out) + assert not results.get('failed') + assert not results['changed'] + + @pytest.mark.parametrize('patch_ansible_module', TESTCASE_CONNECTION, indirect=['patch_ansible_module']) def test_dns4_none(mocked_connection_exists, capfd): """ @@ -2991,6 +3179,38 @@ def test_ethernet_connection_static_ipv4_address_static_route_with_metric_modify assert not results.get('failed') +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_ETHERNET_MOD_IPV4_INT_WITH_ROUTE_AND_METRIC_CLEAR, indirect=['patch_ansible_module']) +def test_ethernet_connection_static_ipv4_address_static_route_with_metric_clear( + mocked_ethernet_connection_with_ipv4_static_address_static_route_metric_clear, capfd): + """ + Test : Modify ethernet connection with static IPv4 address and static route + """ + with pytest.raises(SystemExit): + nmcli.main() + + arg_list = nmcli.Nmcli.execute_command.call_args_list + add_args, add_kw = arg_list[1] + + assert add_args[0][0] == '/usr/bin/nmcli' + assert add_args[0][1] == 'con' + assert add_args[0][2] == 'modify' + assert add_args[0][3] == 'non_existent_nw_device' + + add_args_text = list(map(to_text, add_args[0])) + + for param in ['ipv4.routes', '']: + assert param in add_args_text + + out, err = capfd.readouterr() + results = json.loads(out) + + assert 'ipv4.routes' in results['diff']['before'] + assert 'ipv4.routes' in results['diff']['after'] + + assert results.get('changed') is True + assert not results.get('failed') + + @pytest.mark.parametrize('patch_ansible_module', TESTCASE_ETHERNET_ADD_IPV6_INT_WITH_ROUTE, indirect=['patch_ansible_module']) def test_ethernet_connection_static_ipv6_address_static_route_create(mocked_ethernet_connection_with_ipv6_static_address_static_route_create, capfd): """ @@ -4032,6 +4252,7 @@ def test_bond_connection_unchanged(mocked_generic_connection_diff_check, capfd): state=dict(type='str', required=True, choices=['absent', 'present']), conn_name=dict(type='str', required=True), master=dict(type='str'), + slave_type=dict(type=str, choices=['bond', 'bridge', 'team']), ifname=dict(type='str'), type=dict(type='str', choices=[ @@ -4077,6 +4298,7 @@ def test_bond_connection_unchanged(mocked_generic_connection_diff_check, capfd): never_default4=dict(type='bool', default=False), dns4=dict(type='list', elements='str'), dns4_search=dict(type='list', elements='str'), + dns4_options=dict(type='list', elements='str'), dns4_ignore_auto=dict(type='bool', default=False), method4=dict(type='str', choices=['auto', 'link-local', 'manual', 'shared', 'disabled']), may_fail4=dict(type='bool', default=True), @@ -4086,6 +4308,7 @@ def test_bond_connection_unchanged(mocked_generic_connection_diff_check, capfd): gw6_ignore_auto=dict(type='bool', default=False), dns6=dict(type='list', elements='str'), dns6_search=dict(type='list', elements='str'), + dns6_options=dict(type='list', elements='str'), dns6_ignore_auto=dict(type='bool', default=False), routes6=dict(type='list', elements='str'), routes6_extended=dict(type='list', @@ -4259,3 +4482,366 @@ def test_macvlan_mod(mocked_generic_connection_modify, capfd): results = json.loads(out) assert not results.get('failed') assert results['changed'] + + +TESTCASE_SLAVE_TYPE_BRIDGE_CONNECTION = [ + { + 'type': 'ethernet', + 'conn_name': 'fake_conn', + 'ifname': 'fake_eth0', + 'state': 'present', + 'slave_type': 'bridge', + 'master': 'fake_br0', + '_ansible_check_mode': False, + } +] + + +TESTCASE_SLAVE_TYPE_BRIDGE_CONNECTION_SHOW_OUTPUT = """\ +connection.id: fake_conn +connection.type: 802-3-ethernet +connection.interface-name: fake_eth0 +connection.autoconnect: yes +connection.master: -- +connection.slave-type: -- +802-3-ethernet.mtu: auto +""" + + +TESTCASE_SLAVE_TYPE_BRIDGE_CONNECTION_UNCHANGED_SHOW_OUTPUT = """\ +connection.id: fake_conn +connection.type: 802-3-ethernet +connection.interface-name: fake_eth0 +connection.autoconnect: yes +connection.master: fake_br0 +connection.slave-type: bridge +802-3-ethernet.mtu: auto +""" + + +@pytest.fixture +def mocked_slave_type_bridge_create(mocker): + mocker_set(mocker, + execute_return=None, + execute_side_effect=( + (0, TESTCASE_SLAVE_TYPE_BRIDGE_CONNECTION_SHOW_OUTPUT, ""), + (0, "", ""), + )) + + +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_SLAVE_TYPE_BRIDGE_CONNECTION, indirect=['patch_ansible_module']) +def test_create_slave_type_bridge(mocked_slave_type_bridge_create, capfd): + """ + Test : slave for bridge created + """ + + with pytest.raises(SystemExit): + nmcli.main() + + assert nmcli.Nmcli.execute_command.call_count == 1 + arg_list = nmcli.Nmcli.execute_command.call_args_list + args, kwargs = arg_list[0] + + assert args[0][0] == '/usr/bin/nmcli' + assert args[0][1] == 'con' + assert args[0][2] == 'add' + assert args[0][3] == 'type' + assert args[0][4] == 'ethernet' + assert args[0][5] == 'con-name' + assert args[0][6] == 'fake_conn' + con_master_index = args[0].index('connection.master') + slave_type_index = args[0].index('connection.slave-type') + assert args[0][con_master_index + 1] == 'fake_br0' + assert args[0][slave_type_index + 1] == 'bridge' + + out, err = capfd.readouterr() + results = json.loads(out) + assert not results.get('failed') + assert results['changed'] + + +@pytest.fixture +def mocked_create_slave_type_bridge_unchanged(mocker): + mocker_set(mocker, + connection_exists=True, + execute_return=(0, TESTCASE_SLAVE_TYPE_BRIDGE_CONNECTION_UNCHANGED_SHOW_OUTPUT, "")) + + +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_SLAVE_TYPE_BRIDGE_CONNECTION, indirect=['patch_ansible_module']) +def test_slave_type_bridge_unchanged(mocked_create_slave_type_bridge_unchanged, capfd): + """ + Test : Existent slave for bridge unchanged + """ + with pytest.raises(SystemExit): + nmcli.main() + + out, err = capfd.readouterr() + results = json.loads(out) + assert not results.get('failed') + assert not results['changed'] + + +TESTCASE_SLAVE_TYPE_BOND_CONNECTION = [ + { + 'type': 'ethernet', + 'conn_name': 'fake_conn', + 'ifname': 'fake_eth0', + 'state': 'present', + 'slave_type': 'bond', + 'master': 'fake_bond0', + '_ansible_check_mode': False, + } +] + + +TESTCASE_SLAVE_TYPE_BOND_CONNECTION_SHOW_OUTPUT = """\ +connection.id: fake_conn +connection.type: 802-3-ethernet +connection.interface-name: fake_eth0 +connection.autoconnect: yes +connection.master: -- +connection.slave-type: -- +802-3-ethernet.mtu: auto +""" + + +TESTCASE_SLAVE_TYPE_BOND_CONNECTION_UNCHANGED_SHOW_OUTPUT = """\ +connection.id: fake_conn +connection.type: 802-3-ethernet +connection.interface-name: fake_eth0 +connection.autoconnect: yes +connection.master: fake_bond0 +connection.slave-type: bond +802-3-ethernet.mtu: auto +""" + + +@pytest.fixture +def mocked_slave_type_bond_create(mocker): + mocker_set(mocker, + execute_return=None, + execute_side_effect=( + (0, TESTCASE_SLAVE_TYPE_BOND_CONNECTION_SHOW_OUTPUT, ""), + (0, "", ""), + )) + + +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_SLAVE_TYPE_BOND_CONNECTION, indirect=['patch_ansible_module']) +def test_create_slave_type_bond(mocked_slave_type_bond_create, capfd): + """ + Test : slave for bond created + """ + + with pytest.raises(SystemExit): + nmcli.main() + + assert nmcli.Nmcli.execute_command.call_count == 1 + arg_list = nmcli.Nmcli.execute_command.call_args_list + args, kwargs = arg_list[0] + + assert args[0][0] == '/usr/bin/nmcli' + assert args[0][1] == 'con' + assert args[0][2] == 'add' + assert args[0][3] == 'type' + assert args[0][4] == 'ethernet' + assert args[0][5] == 'con-name' + assert args[0][6] == 'fake_conn' + con_master_index = args[0].index('connection.master') + slave_type_index = args[0].index('connection.slave-type') + assert args[0][con_master_index + 1] == 'fake_bond0' + assert args[0][slave_type_index + 1] == 'bond' + + out, err = capfd.readouterr() + results = json.loads(out) + assert not results.get('failed') + assert results['changed'] + + +@pytest.fixture +def mocked_create_slave_type_bond_unchanged(mocker): + mocker_set(mocker, + connection_exists=True, + execute_return=(0, TESTCASE_SLAVE_TYPE_BOND_CONNECTION_UNCHANGED_SHOW_OUTPUT, "")) + + +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_SLAVE_TYPE_BOND_CONNECTION, indirect=['patch_ansible_module']) +def test_slave_type_bond_unchanged(mocked_create_slave_type_bond_unchanged, capfd): + """ + Test : Existent slave for bridge unchanged + """ + with pytest.raises(SystemExit): + nmcli.main() + + out, err = capfd.readouterr() + results = json.loads(out) + assert not results.get('failed') + assert not results['changed'] + + +TESTCASE_SLAVE_TYPE_TEAM_CONNECTION = [ + { + 'type': 'ethernet', + 'conn_name': 'fake_conn', + 'ifname': 'fake_eth0', + 'state': 'present', + 'slave_type': 'team', + 'master': 'fake_team0', + '_ansible_check_mode': False, + } +] + + +TESTCASE_SLAVE_TYPE_TEAM_CONNECTION_SHOW_OUTPUT = """\ +connection.id: fake_conn +connection.type: 802-3-ethernet +connection.interface-name: fake_eth0 +connection.autoconnect: yes +connection.master: -- +connection.slave-type: -- +802-3-ethernet.mtu: auto +""" + + +TESTCASE_SLAVE_TYPE_TEAM_CONNECTION_UNCHANGED_SHOW_OUTPUT = """\ +connection.id: fake_conn +connection.type: 802-3-ethernet +connection.interface-name: fake_eth0 +connection.autoconnect: yes +connection.master: fake_team0 +connection.slave-type: team +802-3-ethernet.mtu: auto +""" + + +@pytest.fixture +def mocked_slave_type_team_create(mocker): + mocker_set(mocker, + execute_return=None, + execute_side_effect=( + (0, TESTCASE_SLAVE_TYPE_TEAM_CONNECTION_SHOW_OUTPUT, ""), + (0, "", ""), + )) + + +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_SLAVE_TYPE_TEAM_CONNECTION, indirect=['patch_ansible_module']) +def test_create_slave_type_team(mocked_slave_type_team_create, capfd): + """ + Test : slave for bond created + """ + + with pytest.raises(SystemExit): + nmcli.main() + + assert nmcli.Nmcli.execute_command.call_count == 1 + arg_list = nmcli.Nmcli.execute_command.call_args_list + args, kwargs = arg_list[0] + + assert args[0][0] == '/usr/bin/nmcli' + assert args[0][1] == 'con' + assert args[0][2] == 'add' + assert args[0][3] == 'type' + assert args[0][4] == 'ethernet' + assert args[0][5] == 'con-name' + assert args[0][6] == 'fake_conn' + con_master_index = args[0].index('connection.master') + slave_type_index = args[0].index('connection.slave-type') + assert args[0][con_master_index + 1] == 'fake_team0' + assert args[0][slave_type_index + 1] == 'team' + + out, err = capfd.readouterr() + results = json.loads(out) + assert not results.get('failed') + assert results['changed'] + + +@pytest.fixture +def mocked_create_slave_type_team_unchanged(mocker): + mocker_set(mocker, + connection_exists=True, + execute_return=(0, TESTCASE_SLAVE_TYPE_TEAM_CONNECTION_UNCHANGED_SHOW_OUTPUT, "")) + + +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_SLAVE_TYPE_TEAM_CONNECTION, indirect=['patch_ansible_module']) +def test_slave_type_team_unchanged(mocked_create_slave_type_team_unchanged, capfd): + """ + Test : Existent slave for bridge unchanged + """ + with pytest.raises(SystemExit): + nmcli.main() + + out, err = capfd.readouterr() + results = json.loads(out) + assert not results.get('failed') + assert not results['changed'] + + +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_LOOPBACK, indirect=['patch_ansible_module']) +def test_create_loopback(mocked_generic_connection_create, capfd): + """ + Test : Create loopback connection + """ + + with pytest.raises(SystemExit): + nmcli.main() + + assert nmcli.Nmcli.execute_command.call_count == 1 + arg_list = nmcli.Nmcli.execute_command.call_args_list + add_args, add_kw = arg_list[0] + + assert add_args[0][0] == '/usr/bin/nmcli' + assert add_args[0][1] == 'con' + assert add_args[0][2] == 'add' + assert add_args[0][3] == 'type' + assert add_args[0][4] == 'loopback' + assert add_args[0][5] == 'con-name' + assert add_args[0][6] == 'lo' + + add_args_text = list(map(to_text, add_args[0])) + for param in ['connection.interface-name', 'lo', + 'ipv4.addresses', '127.0.0.1/8']: + assert param in add_args_text + + out, err = capfd.readouterr() + results = json.loads(out) + assert not results.get('failed') + assert results['changed'] + + +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_LOOPBACK, indirect=['patch_ansible_module']) +def test_unchanged_loopback(mocked_loopback_connection_unchanged, capfd): + """ + Test : loopback connection unchanged + """ + with pytest.raises(SystemExit): + nmcli.main() + + out, err = capfd.readouterr() + results = json.loads(out) + assert not results.get('failed') + assert not results['changed'] + + +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_LOOPBACK_MODIFY, indirect=['patch_ansible_module']) +def test_add_second_ip4_address_to_loopback_connection(mocked_loopback_connection_modify, capfd): + """ + Test : Modify loopback connection + """ + with pytest.raises(SystemExit): + nmcli.main() + + assert nmcli.Nmcli.execute_command.call_count == 2 + arg_list = nmcli.Nmcli.execute_command.call_args_list + args, kwargs = arg_list[1] + + assert args[0][0] == '/usr/bin/nmcli' + assert args[0][1] == 'con' + assert args[0][2] == 'modify' + assert args[0][3] == 'lo' + + for param in ['ipv4.addresses', '127.0.0.1/8,127.0.0.2/8']: + assert param in args[0] + + out, err = capfd.readouterr() + results = json.loads(out) + assert not results.get('failed') + assert results['changed'] diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_nomad_token.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_nomad_token.py new file mode 100644 index 000000000..48f060f8b --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_nomad_token.py @@ -0,0 +1,222 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2021, 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 nomad +from ansible_collections.community.general.plugins.modules import nomad_token +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 + + +def mock_acl_get_tokens(empty_list=False): + response_object = [] + + if not empty_list: + response_object = [ + { + 'AccessorID': 'bac2b162-2a63-efa2-4e68-55d79dcb7721', + 'Name': 'Bootstrap Token', 'Type': 'management', + 'Policies': None, 'Roles': None, 'Global': True, + 'Hash': 'BUJ3BerTfrqFVm1P+vZr1gz9ubOkd+JAvYjNAJyaU9Y=', + 'CreateTime': '2023-11-12T18:44:39.740562185Z', + 'ExpirationTime': None, + 'CreateIndex': 9, + 'ModifyIndex': 9 + }, + { + 'AccessorID': '0d01c55f-8d63-f832-04ff-1866d4eb594e', + 'Name': 'devs', + 'Type': 'client', 'Policies': ['readonly'], + 'Roles': None, + 'Global': True, + 'Hash': 'eSn8H8RVqh8As8WQNnC2vlBRqXy6DECogc5umzX0P30=', + 'CreateTime': '2023-11-12T18:48:34.248857001Z', + 'ExpirationTime': None, + 'CreateIndex': 14, + 'ModifyIndex': 836 + } + ] + + return response_object + + +def mock_acl_generate_bootstrap(): + response_object = { + 'AccessorID': '0d01c55f-8d63-f832-04ff-1866d4eb594e', + 'Name': 'Bootstrap Token', + 'Type': 'management', + 'Policies': None, + 'Roles': None, + 'Global': True, + 'Hash': 'BUJ3BerTfrqFVm1P+vZr1gz9ubOkd+JAvYjNAJyaU9Y=', + 'CreateTime': '2023-11-12T18:48:34.248857001Z', + 'ExpirationTime': None, + 'ExpirationTTL': '', + 'CreateIndex': 14, + 'ModifyIndex': 836, + 'SecretID': 'd539a03d-337a-8504-6d12-000f861337bc' + } + return response_object + + +def mock_acl_create_update_token(): + response_object = { + 'AccessorID': '0d01c55f-8d63-f832-04ff-1866d4eb594e', + 'Name': 'dev', + 'Type': 'client', + 'Policies': ['readonly'], + 'Roles': None, + 'Global': True, + 'Hash': 'eSn8H8RVqh8As8WQNnC2vlBRqXy6DECogc5umzX0P30=', + 'CreateTime': '2023-11-12T18:48:34.248857001Z', + 'ExpirationTime': None, + 'ExpirationTTL': '', + 'CreateIndex': 14, + 'ModifyIndex': 836, + 'SecretID': 'd539a03d-337a-8504-6d12-000f861337bc' + } + + return response_object + + +def mock_acl_delete_token(): + return {} + + +class TestNomadTokenModule(ModuleTestCase): + + def setUp(self): + super(TestNomadTokenModule, self).setUp() + self.module = nomad_token + + def tearDown(self): + super(TestNomadTokenModule, self).tearDown() + + def test_should_fail_without_parameters(self): + with self.assertRaises(AnsibleFailJson): + set_module_args({}) + self.module.main() + + def test_should_create_token_type_client(self): + module_args = { + 'host': 'localhost', + 'name': 'Dev token', + 'token_type': 'client', + 'state': 'present' + } + + set_module_args(module_args) + with patch.object(nomad.api.acl.Acl, 'get_tokens', return_value=mock_acl_get_tokens()) as mock_get_tokens: + with patch.object(nomad.api.acl.Acl, 'create_token', return_value=mock_acl_create_update_token()) as \ + mock_create_update_token: + with self.assertRaises(AnsibleExitJson): + self.module.main() + + self.assertIs(mock_get_tokens.call_count, 1) + self.assertIs(mock_create_update_token.call_count, 1) + + def test_should_create_token_type_bootstrap(self): + module_args = { + 'host': 'localhost', + 'token_type': 'bootstrap', + 'state': 'present' + } + + set_module_args(module_args) + + with patch.object(nomad.api.acl.Acl, 'get_tokens') as mock_get_tokens: + with patch.object(nomad.api.Acl, 'generate_bootstrap') as mock_generate_bootstrap: + mock_get_tokens.return_value = mock_acl_get_tokens(empty_list=True) + mock_generate_bootstrap.return_value = mock_acl_generate_bootstrap() + + with self.assertRaises(AnsibleExitJson): + self.module.main() + + self.assertIs(mock_get_tokens.call_count, 1) + self.assertIs(mock_generate_bootstrap.call_count, 1) + + def test_should_fail_delete_without_name_parameter(self): + module_args = { + 'host': 'localhost', + 'state': 'absent' + } + + set_module_args(module_args) + with patch.object(nomad.api.acl.Acl, 'get_tokens') as mock_get_tokens: + with patch.object(nomad.api.acl.Acl, 'delete_token') as mock_delete_token: + mock_get_tokens.return_value = mock_acl_get_tokens() + mock_delete_token.return_value = mock_acl_delete_token() + + with self.assertRaises(AnsibleFailJson): + self.module.main() + + def test_should_fail_delete_bootstrap_token(self): + module_args = { + 'host': 'localhost', + 'token_type': 'boostrap', + 'state': 'absent' + } + + set_module_args(module_args) + + with self.assertRaises(AnsibleFailJson): + self.module.main() + + def test_should_fail_delete_boostrap_token_by_name(self): + module_args = { + 'host': 'localhost', + 'name': 'Bootstrap Token', + 'state': 'absent' + } + + set_module_args(module_args) + + with self.assertRaises(AnsibleFailJson): + self.module.main() + + def test_should_delete_client_token(self): + module_args = { + 'host': 'localhost', + 'name': 'devs', + 'state': 'absent' + } + + set_module_args(module_args) + + with patch.object(nomad.api.acl.Acl, 'get_tokens') as mock_get_tokens: + with patch.object(nomad.api.acl.Acl, 'delete_token') as mock_delete_token: + mock_get_tokens.return_value = mock_acl_get_tokens() + mock_delete_token.return_value = mock_acl_delete_token() + + with self.assertRaises(AnsibleExitJson): + self.module.main() + + self.assertIs(mock_delete_token.call_count, 1) + + def test_should_update_client_token(self): + module_args = { + 'host': 'localhost', + 'name': 'devs', + 'token_type': 'client', + 'state': 'present' + } + + set_module_args(module_args) + + with patch.object(nomad.api.acl.Acl, 'get_tokens') as mock_get_tokens: + with patch.object(nomad.api.acl.Acl, 'update_token') as mock_create_update_token: + mock_get_tokens.return_value = mock_acl_get_tokens() + mock_create_update_token.return_value = mock_acl_create_update_token() + + with self.assertRaises(AnsibleExitJson): + self.module.main() + self.assertIs(mock_get_tokens.call_count, 1) + self.assertIs(mock_create_update_token.call_count, 1) diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_npm.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_npm.py index f5d312775..cc4d65172 100644 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_npm.py +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_npm.py @@ -48,8 +48,8 @@ class NPMModuleTestCase(ModuleTestCase): self.assertTrue(result['changed']) self.module_main_command.assert_has_calls([ - call(['/testbin/npm', 'list', '--json', '--long', '--global'], check_rc=False, cwd=None), - call(['/testbin/npm', 'install', '--global', 'coffee-script'], check_rc=True, cwd=None), + call(['/testbin/npm', 'list', '--json', '--long', '--global'], check_rc=False, cwd=None, environ_update={'LANGUAGE': 'C', 'LC_ALL': 'C'}), + call(['/testbin/npm', 'install', '--global', 'coffee-script'], check_rc=True, cwd=None, environ_update={'LANGUAGE': 'C', 'LC_ALL': 'C'}), ]) def test_present_missing(self): @@ -67,8 +67,8 @@ class NPMModuleTestCase(ModuleTestCase): self.assertTrue(result['changed']) self.module_main_command.assert_has_calls([ - call(['/testbin/npm', 'list', '--json', '--long', '--global'], check_rc=False, cwd=None), - call(['/testbin/npm', 'install', '--global', 'coffee-script'], check_rc=True, cwd=None), + call(['/testbin/npm', 'list', '--json', '--long', '--global'], check_rc=False, cwd=None, environ_update={'LANGUAGE': 'C', 'LC_ALL': 'C'}), + call(['/testbin/npm', 'install', '--global', 'coffee-script'], check_rc=True, cwd=None, environ_update={'LANGUAGE': 'C', 'LC_ALL': 'C'}), ]) def test_present_version(self): @@ -87,8 +87,8 @@ class NPMModuleTestCase(ModuleTestCase): self.assertTrue(result['changed']) self.module_main_command.assert_has_calls([ - call(['/testbin/npm', 'list', '--json', '--long', '--global'], check_rc=False, cwd=None), - call(['/testbin/npm', 'install', '--global', 'coffee-script@2.5.1'], check_rc=True, cwd=None), + call(['/testbin/npm', 'list', '--json', '--long', '--global'], check_rc=False, cwd=None, environ_update={'LANGUAGE': 'C', 'LC_ALL': 'C'}), + call(['/testbin/npm', 'install', '--global', 'coffee-script@2.5.1'], check_rc=True, cwd=None, environ_update={'LANGUAGE': 'C', 'LC_ALL': 'C'}), ]) def test_present_version_update(self): @@ -107,8 +107,8 @@ class NPMModuleTestCase(ModuleTestCase): self.assertTrue(result['changed']) self.module_main_command.assert_has_calls([ - call(['/testbin/npm', 'list', '--json', '--long', '--global'], check_rc=False, cwd=None), - call(['/testbin/npm', 'install', '--global', 'coffee-script@2.5.1'], check_rc=True, cwd=None), + call(['/testbin/npm', 'list', '--json', '--long', '--global'], check_rc=False, cwd=None, environ_update={'LANGUAGE': 'C', 'LC_ALL': 'C'}), + call(['/testbin/npm', 'install', '--global', 'coffee-script@2.5.1'], check_rc=True, cwd=None, environ_update={'LANGUAGE': 'C', 'LC_ALL': 'C'}), ]) def test_present_version_exists(self): @@ -127,7 +127,7 @@ class NPMModuleTestCase(ModuleTestCase): self.assertFalse(result['changed']) self.module_main_command.assert_has_calls([ - call(['/testbin/npm', 'list', '--json', '--long', '--global'], check_rc=False, cwd=None), + call(['/testbin/npm', 'list', '--json', '--long', '--global'], check_rc=False, cwd=None, environ_update={'LANGUAGE': 'C', 'LC_ALL': 'C'}), ]) def test_absent(self): @@ -145,8 +145,8 @@ class NPMModuleTestCase(ModuleTestCase): self.assertTrue(result['changed']) self.module_main_command.assert_has_calls([ - call(['/testbin/npm', 'list', '--json', '--long', '--global'], check_rc=False, cwd=None), - call(['/testbin/npm', 'uninstall', '--global', 'coffee-script'], check_rc=True, cwd=None), + call(['/testbin/npm', 'list', '--json', '--long', '--global'], check_rc=False, cwd=None, environ_update={'LANGUAGE': 'C', 'LC_ALL': 'C'}), + call(['/testbin/npm', 'uninstall', '--global', 'coffee-script'], check_rc=True, cwd=None, environ_update={'LANGUAGE': 'C', 'LC_ALL': 'C'}), ]) def test_absent_version(self): @@ -165,8 +165,8 @@ class NPMModuleTestCase(ModuleTestCase): self.assertTrue(result['changed']) self.module_main_command.assert_has_calls([ - call(['/testbin/npm', 'list', '--json', '--long', '--global'], check_rc=False, cwd=None), - call(['/testbin/npm', 'uninstall', '--global', 'coffee-script'], check_rc=True, cwd=None), + call(['/testbin/npm', 'list', '--json', '--long', '--global'], check_rc=False, cwd=None, environ_update={'LANGUAGE': 'C', 'LC_ALL': 'C'}), + call(['/testbin/npm', 'uninstall', '--global', 'coffee-script'], check_rc=True, cwd=None, environ_update={'LANGUAGE': 'C', 'LC_ALL': 'C'}), ]) def test_absent_version_different(self): @@ -185,8 +185,8 @@ class NPMModuleTestCase(ModuleTestCase): self.assertTrue(result['changed']) self.module_main_command.assert_has_calls([ - call(['/testbin/npm', 'list', '--json', '--long', '--global'], check_rc=False, cwd=None), - call(['/testbin/npm', 'uninstall', '--global', 'coffee-script'], check_rc=True, cwd=None), + call(['/testbin/npm', 'list', '--json', '--long', '--global'], check_rc=False, cwd=None, environ_update={'LANGUAGE': 'C', 'LC_ALL': 'C'}), + call(['/testbin/npm', 'uninstall', '--global', 'coffee-script'], check_rc=True, cwd=None, environ_update={'LANGUAGE': 'C', 'LC_ALL': 'C'}), ]) def test_present_package_json(self): @@ -203,7 +203,7 @@ class NPMModuleTestCase(ModuleTestCase): self.assertTrue(result['changed']) self.module_main_command.assert_has_calls([ - call(['/testbin/npm', 'install', '--global'], check_rc=True, cwd=None), + call(['/testbin/npm', 'install', '--global'], check_rc=True, cwd=None, environ_update={'LANGUAGE': 'C', 'LC_ALL': 'C'}), ]) def test_present_package_json_production(self): @@ -221,7 +221,7 @@ class NPMModuleTestCase(ModuleTestCase): self.assertTrue(result['changed']) self.module_main_command.assert_has_calls([ - call(['/testbin/npm', 'install', '--global', '--production'], check_rc=True, cwd=None), + call(['/testbin/npm', 'install', '--global', '--production'], check_rc=True, cwd=None, environ_update={'LANGUAGE': 'C', 'LC_ALL': 'C'}), ]) def test_present_package_json_ci(self): @@ -239,7 +239,7 @@ class NPMModuleTestCase(ModuleTestCase): self.assertTrue(result['changed']) self.module_main_command.assert_has_calls([ - call(['/testbin/npm', 'ci', '--global'], check_rc=True, cwd=None), + call(['/testbin/npm', 'ci', '--global'], check_rc=True, cwd=None, environ_update={'LANGUAGE': 'C', 'LC_ALL': 'C'}), ]) def test_present_package_json_ci_production(self): @@ -258,5 +258,5 @@ class NPMModuleTestCase(ModuleTestCase): self.assertTrue(result['changed']) self.module_main_command.assert_has_calls([ - call(['/testbin/npm', 'ci', '--global', '--production'], check_rc=True, cwd=None), + call(['/testbin/npm', 'ci', '--global', '--production'], check_rc=True, cwd=None, environ_update={'LANGUAGE': 'C', 'LC_ALL': 'C'}), ]) diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_opkg.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_opkg.py index 8e52368ff..c42025959 100644 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_opkg.py +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_opkg.py @@ -6,236 +6,9 @@ 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 +from .helper import Helper -import pytest -TESTED_MODULE = opkg.__name__ - - -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] - - -@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_opkg(mocker, capfd, patch_opkg, testcase): - """ - Run unit tests for test cases listen 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 +Helper.from_module(opkg, __name__) diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_opkg.yaml b/ansible_collections/community/general/tests/unit/plugins/modules/test_opkg.yaml new file mode 100644 index 000000000..6e227dea2 --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_opkg.yaml @@ -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: &env-def {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: false} + rc: 0 + out: "" + err: "" + - command: [/testbin/opkg, install, zlib-dev] + environ: *env-def + 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: *env-def + 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: *env-def + 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: *env-def + rc: 0 + out: | + zlib-dev - 1.2.11-6 + err: "" + - command: [/testbin/opkg, install, --force-reinstall, zlib-dev] + environ: *env-def + 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: *env-def + 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: *env-def + rc: 0 + out: "" + err: "" + - command: [/testbin/opkg, install, zlib-dev=1.2.11-6] + environ: *env-def + 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: *env-def + 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: *env-def + rc: 0 + out: "" + err: "" + - command: [/testbin/opkg, list-installed, vim-fuller] + environ: *env-def + rc: 0 + out: "" + err: "" + - command: [/testbin/opkg, install, vim-fuller] + environ: *env-def + 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: *env-def + 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: "" diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_pagerduty.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_pagerduty.py index d363804bc..75987d3df 100644 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_pagerduty.py +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_pagerduty.py @@ -20,9 +20,9 @@ class PagerDutyTest(unittest.TestCase): return object(), {'status': 200} def _assert_ongoing_window_with_v1_compatible_header(self, module, url, headers, data=None, method=None): - self.assertDictContainsSubset( - {'Accept': 'application/vnd.pagerduty+json;version=2'}, - headers, + self.assertEqual( + 'application/vnd.pagerduty+json;version=2', + headers.get('Accept'), 'Accept:application/vnd.pagerduty+json;version=2 HTTP header not found' ) return object(), {'status': 200} @@ -36,17 +36,17 @@ class PagerDutyTest(unittest.TestCase): return object(), {'status': 201} def _assert_create_a_maintenance_window_from_header(self, module, url, headers, data=None, method=None): - self.assertDictContainsSubset( - {'From': 'requester_id'}, - headers, + self.assertEqual( + 'requester_id', + headers.get('From'), 'From:requester_id HTTP header not found' ) return object(), {'status': 201} def _assert_create_window_with_v1_compatible_header(self, module, url, headers, data=None, method=None): - self.assertDictContainsSubset( - {'Accept': 'application/vnd.pagerduty+json;version=2'}, - headers, + self.assertEqual( + 'application/vnd.pagerduty+json;version=2', + headers.get('Accept'), 'Accept:application/vnd.pagerduty+json;version=2 HTTP header not found' ) return object(), {'status': 201} @@ -54,9 +54,9 @@ class PagerDutyTest(unittest.TestCase): def _assert_create_window_payload(self, module, url, headers, data=None, method=None): payload = json.loads(data) window_data = payload['maintenance_window'] - self.assertTrue('start_time' in window_data, '"start_time" is requiered attribute') - self.assertTrue('end_time' in window_data, '"end_time" is requiered attribute') - self.assertTrue('services' in window_data, '"services" is requiered attribute') + self.assertTrue('start_time' in window_data, '"start_time" is required attribute') + self.assertTrue('end_time' in window_data, '"end_time" is required attribute') + self.assertTrue('services' in window_data, '"services" is required attribute') return object(), {'status': 201} def _assert_create_window_single_service(self, module, url, headers, data=None, method=None): @@ -89,9 +89,9 @@ class PagerDutyTest(unittest.TestCase): return object(), {'status': 204} def _assert_absent_window_with_v1_compatible_header(self, module, url, headers, method=None): - self.assertDictContainsSubset( - {'Accept': 'application/vnd.pagerduty+json;version=2'}, - headers, + self.assertEqual( + 'application/vnd.pagerduty+json;version=2', + headers.get('Accept'), 'Accept:application/vnd.pagerduty+json;version=2 HTTP header not found' ) return object(), {'status': 204} diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_pagerduty_alert.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_pagerduty_alert.py index 3df992b42..7a1e951a2 100644 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_pagerduty_alert.py +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_pagerduty_alert.py @@ -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): @@ -18,9 +22,9 @@ class PagerDutyAlertsTest(unittest.TestCase): return Response(), {'status': 200} def _assert_compatibility_header(self, module, url, method, headers): - self.assertDictContainsSubset( - {'Accept': 'application/vnd.pagerduty+json;version=2'}, - headers, + self.assertEqual( + 'application/vnd.pagerduty+json;version=2', + headers.get('Accept'), 'Accept:application/vnd.pagerduty+json;version=2 HTTP header not found' ) return Response(), {'status': 200} @@ -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' diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_pkgin.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_pkgin.py index d73911e0c..dea5a05b5 100644 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_pkgin.py +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_pkgin.py @@ -30,7 +30,7 @@ class TestPkginQueryPackage(unittest.TestCase): command_result = pkgin.query_package(mock_module, package) # then - self.assertEquals(command_result, pkgin.PackageState.PRESENT) + self.assertEqual(command_result, pkgin.PackageState.PRESENT) @mock.patch('ansible_collections.community.general.plugins.modules.pkgin.AnsibleModule') def test_package_with_version_is_present(self, mock_module): @@ -46,7 +46,7 @@ class TestPkginQueryPackage(unittest.TestCase): command_result = pkgin.query_package(mock_module, package) # then - self.assertEquals(command_result, pkgin.PackageState.PRESENT) + self.assertEqual(command_result, pkgin.PackageState.PRESENT) @mock.patch('ansible_collections.community.general.plugins.modules.pkgin.AnsibleModule') def test_package_found_but_not_installed(self, mock_module): @@ -62,7 +62,7 @@ class TestPkginQueryPackage(unittest.TestCase): command_result = pkgin.query_package(mock_module, package) # then - self.assertEquals(command_result, pkgin.PackageState.NOT_INSTALLED) + self.assertEqual(command_result, pkgin.PackageState.NOT_INSTALLED) @mock.patch('ansible_collections.community.general.plugins.modules.pkgin.AnsibleModule') def test_package_found_outdated(self, mock_module): @@ -78,7 +78,7 @@ class TestPkginQueryPackage(unittest.TestCase): command_result = pkgin.query_package(mock_module, package) # then - self.assertEquals(command_result, pkgin.PackageState.OUTDATED) + self.assertEqual(command_result, pkgin.PackageState.OUTDATED) @mock.patch('ansible_collections.community.general.plugins.modules.pkgin.AnsibleModule') def test_package_with_version_found_outdated(self, mock_module): @@ -94,7 +94,7 @@ class TestPkginQueryPackage(unittest.TestCase): command_result = pkgin.query_package(mock_module, package) # then - self.assertEquals(command_result, pkgin.PackageState.OUTDATED) + self.assertEqual(command_result, pkgin.PackageState.OUTDATED) @mock.patch('ansible_collections.community.general.plugins.modules.pkgin.AnsibleModule') def test_package_not_found(self, mock_module): @@ -110,7 +110,7 @@ class TestPkginQueryPackage(unittest.TestCase): command_result = pkgin.query_package(mock_module, package) # then - self.assertEquals(command_result, pkgin.PackageState.NOT_FOUND) + self.assertEqual(command_result, pkgin.PackageState.NOT_FOUND) @mock.patch('ansible_collections.community.general.plugins.modules.pkgin.AnsibleModule') def test_with_parseable_flag_supported_package_is_present(self, mock_module): @@ -126,7 +126,7 @@ class TestPkginQueryPackage(unittest.TestCase): command_result = pkgin.query_package(mock_module, package) # then - self.assertEquals(command_result, pkgin.PackageState.PRESENT) + self.assertEqual(command_result, pkgin.PackageState.PRESENT) @mock.patch('ansible_collections.community.general.plugins.modules.pkgin.AnsibleModule') def test_with_parseable_flag_not_supported_package_is_present(self, mock_module): @@ -142,4 +142,4 @@ class TestPkginQueryPackage(unittest.TestCase): command_result = pkgin.query_package(mock_module, package) # then - self.assertEquals(command_result, pkgin.PackageState.PRESENT) + self.assertEqual(command_result, pkgin.PackageState.PRESENT) diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_proxmox_kvm.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_proxmox_kvm.py index 531185102..4e2cf032c 100644 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_proxmox_kvm.py +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_proxmox_kvm.py @@ -4,17 +4,165 @@ # 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) +from __future__ import absolute_import, division, print_function + __metaclass__ = type -from ansible_collections.community.general.plugins.modules.proxmox_kvm import parse_dev, parse_mac +import sys + +import pytest + +proxmoxer = pytest.importorskip("proxmoxer") +mandatory_py_version = pytest.mark.skipif( + sys.version_info < (2, 7), + reason="The proxmoxer dependency requires python2.7 or higher", +) + +from ansible_collections.community.general.plugins.modules import proxmox_kvm +from ansible_collections.community.general.tests.unit.compat.mock import ( + patch, + DEFAULT, +) +from ansible_collections.community.general.tests.unit.plugins.modules.utils import ( + AnsibleExitJson, + AnsibleFailJson, + ModuleTestCase, + set_module_args, +) +import ansible_collections.community.general.plugins.module_utils.proxmox as proxmox_utils + + +class TestProxmoxKvmModule(ModuleTestCase): + def setUp(self): + super(TestProxmoxKvmModule, self).setUp() + proxmox_utils.HAS_PROXMOXER = True + self.module = proxmox_kvm + self.connect_mock = patch( + "ansible_collections.community.general.plugins.module_utils.proxmox.ProxmoxAnsible._connect" + ).start() + self.get_node_mock = patch.object( + proxmox_utils.ProxmoxAnsible, "get_node" + ).start() + self.get_vm_mock = patch.object(proxmox_utils.ProxmoxAnsible, "get_vm").start() + self.create_vm_mock = patch.object( + proxmox_kvm.ProxmoxKvmAnsible, "create_vm" + ).start() + + def tearDown(self): + self.create_vm_mock.stop() + self.get_vm_mock.stop() + self.get_node_mock.stop() + self.connect_mock.stop() + super(TestProxmoxKvmModule, self).tearDown() + + def test_module_fail_when_required_args_missing(self): + with self.assertRaises(AnsibleFailJson): + set_module_args({}) + self.module.main() + + def test_module_exits_unchaged_when_provided_vmid_exists(self): + set_module_args( + { + "api_host": "host", + "api_user": "user", + "api_password": "password", + "vmid": "100", + "node": "pve", + } + ) + self.get_vm_mock.return_value = [{"vmid": "100"}] + with pytest.raises(AnsibleExitJson) as exc_info: + self.module.main() + + assert self.get_vm_mock.call_count == 1 + result = exc_info.value.args[0] + assert result["changed"] is False + assert result["msg"] == "VM with vmid <100> already exists" + + def test_vm_created_when_vmid_not_exist_but_name_already_exist(self): + set_module_args( + { + "api_host": "host", + "api_user": "user", + "api_password": "password", + "vmid": "100", + "name": "existing.vm.local", + "node": "pve", + } + ) + self.get_vm_mock.return_value = None + with pytest.raises(AnsibleExitJson) as exc_info: + self.module.main() + + assert self.get_vm_mock.call_count == 1 + assert self.get_node_mock.call_count == 1 + result = exc_info.value.args[0] + assert result["changed"] is True + assert result["msg"] == "VM existing.vm.local with vmid 100 deployed" + + def test_vm_not_created_when_name_already_exist_and_vmid_not_set(self): + set_module_args( + { + "api_host": "host", + "api_user": "user", + "api_password": "password", + "name": "existing.vm.local", + "node": "pve", + } + ) + with patch.object(proxmox_utils.ProxmoxAnsible, "get_vmid") as get_vmid_mock: + get_vmid_mock.return_value = { + "vmid": 100, + "name": "existing.vm.local", + } + with pytest.raises(AnsibleExitJson) as exc_info: + self.module.main() + + assert get_vmid_mock.call_count == 1 + result = exc_info.value.args[0] + assert result["changed"] is False + def test_vm_created_when_name_doesnt_exist_and_vmid_not_set(self): + set_module_args( + { + "api_host": "host", + "api_user": "user", + "api_password": "password", + "name": "existing.vm.local", + "node": "pve", + } + ) + self.get_vm_mock.return_value = None + with patch.multiple( + proxmox_utils.ProxmoxAnsible, get_vmid=DEFAULT, get_nextvmid=DEFAULT + ) as utils_mock: + utils_mock["get_vmid"].return_value = None + utils_mock["get_nextvmid"].return_value = 101 + with pytest.raises(AnsibleExitJson) as exc_info: + self.module.main() -def test_parse_mac(): - assert parse_mac('virtio=00:11:22:AA:BB:CC,bridge=vmbr0,firewall=1') == '00:11:22:AA:BB:CC' + assert utils_mock["get_vmid"].call_count == 1 + assert utils_mock["get_nextvmid"].call_count == 1 + result = exc_info.value.args[0] + assert result["changed"] is True + assert result["msg"] == "VM existing.vm.local with vmid 101 deployed" + def test_parse_mac(self): + assert ( + proxmox_kvm.parse_mac("virtio=00:11:22:AA:BB:CC,bridge=vmbr0,firewall=1") + == "00:11:22:AA:BB:CC" + ) -def test_parse_dev(): - assert parse_dev('local-lvm:vm-1000-disk-0,format=qcow2') == 'local-lvm:vm-1000-disk-0' - assert parse_dev('local-lvm:vm-101-disk-1,size=8G') == 'local-lvm:vm-101-disk-1' - assert parse_dev('local-zfs:vm-1001-disk-0') == 'local-zfs:vm-1001-disk-0' + def test_parse_dev(self): + assert ( + proxmox_kvm.parse_dev("local-lvm:vm-1000-disk-0,format=qcow2") + == "local-lvm:vm-1000-disk-0" + ) + assert ( + proxmox_kvm.parse_dev("local-lvm:vm-101-disk-1,size=8G") + == "local-lvm:vm-101-disk-1" + ) + assert ( + proxmox_kvm.parse_dev("local-zfs:vm-1001-disk-0") + == "local-zfs:vm-1001-disk-0" + ) diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_proxmox_snap.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_proxmox_snap.py index 4bdcaa8b7..545fcd1f5 100644 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_proxmox_snap.py +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_proxmox_snap.py @@ -7,8 +7,16 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type import json +import sys + import pytest +proxmoxer = pytest.importorskip('proxmoxer') +mandatory_py_version = pytest.mark.skipif( + sys.version_info < (2, 7), + reason='The proxmoxer dependency requires python2.7 or higher' +) + from ansible_collections.community.general.tests.unit.compat.mock import MagicMock, patch from ansible_collections.community.general.plugins.modules import proxmox_snap import ansible_collections.community.general.plugins.module_utils.proxmox as proxmox_utils diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_proxmox_storage_contents_info.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_proxmox_storage_contents_info.py new file mode 100644 index 000000000..df2625dba --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_proxmox_storage_contents_info.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2023, Julian Vanden Broeck <julian.vandenbroeck at dalibo.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 + +import pytest + +proxmoxer = pytest.importorskip("proxmoxer") + +from ansible_collections.community.general.plugins.modules import proxmox_storage_contents_info +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, +) +import ansible_collections.community.general.plugins.module_utils.proxmox as proxmox_utils + +NODE1 = "pve" +RAW_LIST_OUTPUT = [ + { + "content": "backup", + "ctime": 1702528474, + "format": "pbs-vm", + "size": 273804166061, + "subtype": "qemu", + "vmid": 931, + "volid": "datastore:backup/vm/931/2023-12-14T04:34:34Z", + }, + { + "content": "backup", + "ctime": 1702582560, + "format": "pbs-vm", + "size": 273804166059, + "subtype": "qemu", + "vmid": 931, + "volid": "datastore:backup/vm/931/2023-12-14T19:36:00Z", + }, +] + + +def get_module_args(node, storage, content="all", vmid=None): + return { + "api_host": "host", + "api_user": "user", + "api_password": "password", + "node": node, + "storage": storage, + "content": content, + "vmid": vmid, + } + + +class TestProxmoxStorageContentsInfo(ModuleTestCase): + def setUp(self): + super(TestProxmoxStorageContentsInfo, self).setUp() + proxmox_utils.HAS_PROXMOXER = True + self.module = proxmox_storage_contents_info + self.connect_mock = patch( + "ansible_collections.community.general.plugins.module_utils.proxmox.ProxmoxAnsible._connect", + ).start() + self.connect_mock.return_value.nodes.return_value.storage.return_value.content.return_value.get.return_value = ( + RAW_LIST_OUTPUT + ) + self.connect_mock.return_value.nodes.get.return_value = [{"node": NODE1}] + + def tearDown(self): + self.connect_mock.stop() + super(TestProxmoxStorageContentsInfo, self).tearDown() + + def test_module_fail_when_required_args_missing(self): + with pytest.raises(AnsibleFailJson) as exc_info: + set_module_args({}) + self.module.main() + + def test_storage_contents_info(self): + with pytest.raises(AnsibleExitJson) as exc_info: + set_module_args(get_module_args(node=NODE1, storage="datastore")) + expected_output = {} + self.module.main() + + result = exc_info.value.args[0] + assert not result["changed"] + assert result["proxmox_storage_content"] == RAW_LIST_OUTPUT diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_proxmox_tasks_info.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_proxmox_tasks_info.py index 0d1b5a7bf..5c228655b 100644 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_proxmox_tasks_info.py +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_proxmox_tasks_info.py @@ -10,8 +10,16 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -import pytest import json +import sys + +import pytest + +proxmoxer = pytest.importorskip('proxmoxer') +mandatory_py_version = pytest.mark.skipif( + sys.version_info < (2, 7), + reason='The proxmoxer dependency requires python2.7 or higher' +) from ansible_collections.community.general.plugins.modules import proxmox_tasks_info import ansible_collections.community.general.plugins.module_utils.proxmox as proxmox_utils diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_proxmox_template.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_proxmox_template.py new file mode 100644 index 000000000..dc09a44b3 --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_proxmox_template.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2023, Sergei Antipov <greendayonfire at gmail.com> +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import os +import sys + +import pytest + +proxmoxer = pytest.importorskip('proxmoxer') +mandatory_py_version = pytest.mark.skipif( + sys.version_info < (2, 7), + reason='The proxmoxer dependency requires python2.7 or higher' +) + +from ansible_collections.community.general.plugins.modules import proxmox_template +from ansible_collections.community.general.tests.unit.compat.mock import patch, Mock +from ansible_collections.community.general.tests.unit.plugins.modules.utils import ( + AnsibleFailJson, + ModuleTestCase, + set_module_args, +) +import ansible_collections.community.general.plugins.module_utils.proxmox as proxmox_utils + + +class TestProxmoxTemplateModule(ModuleTestCase): + def setUp(self): + super(TestProxmoxTemplateModule, self).setUp() + proxmox_utils.HAS_PROXMOXER = True + self.module = proxmox_template + self.connect_mock = patch( + "ansible_collections.community.general.plugins.module_utils.proxmox.ProxmoxAnsible._connect" + ) + self.connect_mock.start() + + def tearDown(self): + self.connect_mock.stop() + super(TestProxmoxTemplateModule, self).tearDown() + + @patch("os.stat") + @patch.multiple(os.path, exists=Mock(return_value=True), isfile=Mock(return_value=True)) + def test_module_fail_when_toolbelt_not_installed_and_file_size_is_big(self, mock_stat): + self.module.HAS_REQUESTS_TOOLBELT = False + mock_stat.return_value.st_size = 268435460 + set_module_args( + { + "api_host": "host", + "api_user": "user", + "api_password": "password", + "node": "pve", + "src": "/tmp/mock.iso", + "content_type": "iso" + } + ) + with pytest.raises(AnsibleFailJson) as exc_info: + self.module.main() + + result = exc_info.value.args[0] + assert result["failed"] is True + assert result["msg"] == "'requests_toolbelt' module is required to upload files larger than 256MB" diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_proxmox_vm_info.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_proxmox_vm_info.py new file mode 100644 index 000000000..94bbbc948 --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_proxmox_vm_info.py @@ -0,0 +1,714 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2023, Sergei Antipov <greendayonfire at gmail.com> +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import sys + +import pytest + +proxmoxer = pytest.importorskip("proxmoxer") +mandatory_py_version = pytest.mark.skipif( + sys.version_info < (2, 7), + reason="The proxmoxer dependency requires python2.7 or higher", +) + +from ansible_collections.community.general.plugins.modules import proxmox_vm_info +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, +) +import ansible_collections.community.general.plugins.module_utils.proxmox as proxmox_utils + +NODE1 = "pve" +NODE2 = "pve2" +RAW_CLUSTER_OUTPUT = [ + { + "cpu": 0.174069059487628, + "disk": 0, + "diskread": 6656, + "diskwrite": 0, + "id": "qemu/100", + "maxcpu": 1, + "maxdisk": 34359738368, + "maxmem": 4294967296, + "mem": 35304543, + "name": "pxe.home.arpa", + "netin": 416956, + "netout": 17330, + "node": NODE1, + "status": "running", + "template": 0, + "type": "qemu", + "uptime": 669, + "vmid": 100, + }, + { + "cpu": 0, + "disk": 0, + "diskread": 0, + "diskwrite": 0, + "id": "qemu/101", + "maxcpu": 1, + "maxdisk": 0, + "maxmem": 536870912, + "mem": 0, + "name": "test1", + "netin": 0, + "netout": 0, + "node": NODE2, + "pool": "pool1", + "status": "stopped", + "template": 0, + "type": "qemu", + "uptime": 0, + "vmid": 101, + }, + { + "cpu": 0, + "disk": 352190464, + "diskread": 0, + "diskwrite": 0, + "id": "lxc/102", + "maxcpu": 2, + "maxdisk": 10737418240, + "maxmem": 536870912, + "mem": 28192768, + "name": "test-lxc.home.arpa", + "netin": 102757, + "netout": 446, + "node": NODE1, + "status": "running", + "template": 0, + "type": "lxc", + "uptime": 161, + "vmid": 102, + }, + { + "cpu": 0, + "disk": 0, + "diskread": 0, + "diskwrite": 0, + "id": "lxc/103", + "maxcpu": 2, + "maxdisk": 10737418240, + "maxmem": 536870912, + "mem": 0, + "name": "test1-lxc.home.arpa", + "netin": 0, + "netout": 0, + "node": NODE2, + "pool": "pool1", + "status": "stopped", + "template": 0, + "type": "lxc", + "uptime": 0, + "vmid": 103, + }, + { + "cpu": 0, + "disk": 0, + "diskread": 0, + "diskwrite": 0, + "id": "lxc/104", + "maxcpu": 2, + "maxdisk": 10737418240, + "maxmem": 536870912, + "mem": 0, + "name": "test-lxc.home.arpa", + "netin": 0, + "netout": 0, + "node": NODE2, + "pool": "pool1", + "status": "stopped", + "template": 0, + "type": "lxc", + "uptime": 0, + "vmid": 104, + }, + { + "cpu": 0, + "disk": 0, + "diskread": 0, + "diskwrite": 0, + "id": "lxc/105", + "maxcpu": 2, + "maxdisk": 10737418240, + "maxmem": 536870912, + "mem": 0, + "name": "", + "netin": 0, + "netout": 0, + "node": NODE2, + "pool": "pool1", + "status": "stopped", + "template": 0, + "type": "lxc", + "uptime": 0, + "vmid": 105, + }, +] +RAW_LXC_OUTPUT = [ + { + "cpu": 0, + "cpus": 2, + "disk": 0, + "diskread": 0, + "diskwrite": 0, + "maxdisk": 10737418240, + "maxmem": 536870912, + "maxswap": 536870912, + "mem": 0, + "name": "test1-lxc.home.arpa", + "netin": 0, + "netout": 0, + "status": "stopped", + "swap": 0, + "type": "lxc", + "uptime": 0, + "vmid": "103", + }, + { + "cpu": 0, + "cpus": 2, + "disk": 352190464, + "diskread": 0, + "diskwrite": 0, + "maxdisk": 10737418240, + "maxmem": 536870912, + "maxswap": 536870912, + "mem": 28192768, + "name": "test-lxc.home.arpa", + "netin": 102757, + "netout": 446, + "pid": 4076752, + "status": "running", + "swap": 0, + "type": "lxc", + "uptime": 161, + "vmid": "102", + }, + { + "cpu": 0, + "cpus": 2, + "disk": 0, + "diskread": 0, + "diskwrite": 0, + "maxdisk": 10737418240, + "maxmem": 536870912, + "maxswap": 536870912, + "mem": 0, + "name": "test-lxc.home.arpa", + "netin": 0, + "netout": 0, + "status": "stopped", + "swap": 0, + "type": "lxc", + "uptime": 0, + "vmid": "104", + }, + { + "cpu": 0, + "cpus": 2, + "disk": 0, + "diskread": 0, + "diskwrite": 0, + "maxdisk": 10737418240, + "maxmem": 536870912, + "maxswap": 536870912, + "mem": 0, + "name": "", + "netin": 0, + "netout": 0, + "status": "stopped", + "swap": 0, + "type": "lxc", + "uptime": 0, + "vmid": "105", + }, +] +RAW_QEMU_OUTPUT = [ + { + "cpu": 0, + "cpus": 1, + "disk": 0, + "diskread": 0, + "diskwrite": 0, + "maxdisk": 0, + "maxmem": 536870912, + "mem": 0, + "name": "test1", + "netin": 0, + "netout": 0, + "status": "stopped", + "uptime": 0, + "vmid": 101, + }, + { + "cpu": 0.174069059487628, + "cpus": 1, + "disk": 0, + "diskread": 6656, + "diskwrite": 0, + "maxdisk": 34359738368, + "maxmem": 4294967296, + "mem": 35304543, + "name": "pxe.home.arpa", + "netin": 416956, + "netout": 17330, + "pid": 4076688, + "status": "running", + "uptime": 669, + "vmid": 100, + }, +] +EXPECTED_VMS_OUTPUT = [ + { + "cpu": 0.174069059487628, + "cpus": 1, + "disk": 0, + "diskread": 6656, + "diskwrite": 0, + "id": "qemu/100", + "maxcpu": 1, + "maxdisk": 34359738368, + "maxmem": 4294967296, + "mem": 35304543, + "name": "pxe.home.arpa", + "netin": 416956, + "netout": 17330, + "node": NODE1, + "pid": 4076688, + "status": "running", + "template": False, + "type": "qemu", + "uptime": 669, + "vmid": 100, + }, + { + "cpu": 0, + "cpus": 1, + "disk": 0, + "diskread": 0, + "diskwrite": 0, + "id": "qemu/101", + "maxcpu": 1, + "maxdisk": 0, + "maxmem": 536870912, + "mem": 0, + "name": "test1", + "netin": 0, + "netout": 0, + "node": NODE2, + "pool": "pool1", + "status": "stopped", + "template": False, + "type": "qemu", + "uptime": 0, + "vmid": 101, + }, + { + "cpu": 0, + "cpus": 2, + "disk": 352190464, + "diskread": 0, + "diskwrite": 0, + "id": "lxc/102", + "maxcpu": 2, + "maxdisk": 10737418240, + "maxmem": 536870912, + "maxswap": 536870912, + "mem": 28192768, + "name": "test-lxc.home.arpa", + "netin": 102757, + "netout": 446, + "node": NODE1, + "pid": 4076752, + "status": "running", + "swap": 0, + "template": False, + "type": "lxc", + "uptime": 161, + "vmid": 102, + }, + { + "cpu": 0, + "cpus": 2, + "disk": 0, + "diskread": 0, + "diskwrite": 0, + "id": "lxc/103", + "maxcpu": 2, + "maxdisk": 10737418240, + "maxmem": 536870912, + "maxswap": 536870912, + "mem": 0, + "name": "test1-lxc.home.arpa", + "netin": 0, + "netout": 0, + "node": NODE2, + "pool": "pool1", + "status": "stopped", + "swap": 0, + "template": False, + "type": "lxc", + "uptime": 0, + "vmid": 103, + }, + { + "cpu": 0, + "cpus": 2, + "disk": 0, + "diskread": 0, + "diskwrite": 0, + "id": "lxc/104", + "maxcpu": 2, + "maxdisk": 10737418240, + "maxmem": 536870912, + "maxswap": 536870912, + "mem": 0, + "name": "test-lxc.home.arpa", + "netin": 0, + "netout": 0, + "node": NODE2, + "pool": "pool1", + "status": "stopped", + "swap": 0, + "template": False, + "type": "lxc", + "uptime": 0, + "vmid": 104, + }, + { + "cpu": 0, + "cpus": 2, + "disk": 0, + "diskread": 0, + "diskwrite": 0, + "id": "lxc/105", + "maxcpu": 2, + "maxdisk": 10737418240, + "maxmem": 536870912, + "maxswap": 536870912, + "mem": 0, + "name": "", + "netin": 0, + "netout": 0, + "node": NODE2, + "pool": "pool1", + "status": "stopped", + "swap": 0, + "template": False, + "type": "lxc", + "uptime": 0, + "vmid": 105, + }, +] + + +def get_module_args(type="all", node=None, vmid=None, name=None, config="none"): + return { + "api_host": "host", + "api_user": "user", + "api_password": "password", + "node": node, + "type": type, + "vmid": vmid, + "name": name, + "config": config, + } + + +class TestProxmoxVmInfoModule(ModuleTestCase): + def setUp(self): + super(TestProxmoxVmInfoModule, self).setUp() + proxmox_utils.HAS_PROXMOXER = True + self.module = proxmox_vm_info + self.connect_mock = patch( + "ansible_collections.community.general.plugins.module_utils.proxmox.ProxmoxAnsible._connect", + ).start() + self.connect_mock.return_value.nodes.return_value.lxc.return_value.get.return_value = ( + RAW_LXC_OUTPUT + ) + self.connect_mock.return_value.nodes.return_value.qemu.return_value.get.return_value = ( + RAW_QEMU_OUTPUT + ) + self.connect_mock.return_value.cluster.return_value.resources.return_value.get.return_value = ( + RAW_CLUSTER_OUTPUT + ) + self.connect_mock.return_value.nodes.get.return_value = [{"node": NODE1}] + + def tearDown(self): + self.connect_mock.stop() + super(TestProxmoxVmInfoModule, self).tearDown() + + def test_module_fail_when_required_args_missing(self): + with pytest.raises(AnsibleFailJson) as exc_info: + set_module_args({}) + self.module.main() + + result = exc_info.value.args[0] + assert result["msg"] == "missing required arguments: api_host, api_user" + + def test_get_lxc_vms_information(self): + with pytest.raises(AnsibleExitJson) as exc_info: + set_module_args(get_module_args(type="lxc")) + expected_output = [vm for vm in EXPECTED_VMS_OUTPUT if vm["type"] == "lxc"] + self.module.main() + + result = exc_info.value.args[0] + assert result["changed"] is False + assert result["proxmox_vms"] == expected_output + + def test_get_qemu_vms_information(self): + with pytest.raises(AnsibleExitJson) as exc_info: + set_module_args(get_module_args(type="qemu")) + expected_output = [vm for vm in EXPECTED_VMS_OUTPUT if vm["type"] == "qemu"] + self.module.main() + + result = exc_info.value.args[0] + assert result["proxmox_vms"] == expected_output + + def test_get_all_vms_information(self): + with pytest.raises(AnsibleExitJson) as exc_info: + set_module_args(get_module_args()) + self.module.main() + + result = exc_info.value.args[0] + assert result["proxmox_vms"] == EXPECTED_VMS_OUTPUT + + def test_vmid_is_converted_to_int(self): + with pytest.raises(AnsibleExitJson) as exc_info: + set_module_args(get_module_args(type="lxc")) + self.module.main() + + result = exc_info.value.args[0] + assert isinstance(result["proxmox_vms"][0]["vmid"], int) + + def test_get_specific_lxc_vm_information(self): + with pytest.raises(AnsibleExitJson) as exc_info: + vmid = 102 + expected_output = [ + vm + for vm in EXPECTED_VMS_OUTPUT + if vm["vmid"] == vmid and vm["type"] == "lxc" + ] + set_module_args(get_module_args(type="lxc", vmid=vmid)) + self.module.main() + + result = exc_info.value.args[0] + assert result["proxmox_vms"] == expected_output + assert len(result["proxmox_vms"]) == 1 + + def test_get_specific_qemu_vm_information(self): + with pytest.raises(AnsibleExitJson) as exc_info: + vmid = 100 + expected_output = [ + vm + for vm in EXPECTED_VMS_OUTPUT + if vm["vmid"] == vmid and vm["type"] == "qemu" + ] + set_module_args(get_module_args(type="qemu", vmid=vmid)) + self.module.main() + + result = exc_info.value.args[0] + assert result["proxmox_vms"] == expected_output + assert len(result["proxmox_vms"]) == 1 + + def test_get_specific_vm_information(self): + with pytest.raises(AnsibleExitJson) as exc_info: + vmid = 100 + expected_output = [vm for vm in EXPECTED_VMS_OUTPUT if vm["vmid"] == vmid] + set_module_args(get_module_args(type="all", vmid=vmid)) + self.module.main() + + result = exc_info.value.args[0] + assert result["proxmox_vms"] == expected_output + assert len(result["proxmox_vms"]) == 1 + + def test_get_specific_vm_information_by_using_name(self): + name = "test1-lxc.home.arpa" + self.connect_mock.return_value.cluster.resources.get.return_value = [ + {"name": name, "vmid": "103"} + ] + + with pytest.raises(AnsibleExitJson) as exc_info: + expected_output = [vm for vm in EXPECTED_VMS_OUTPUT if vm["name"] == name] + set_module_args(get_module_args(type="all", name=name)) + self.module.main() + + result = exc_info.value.args[0] + assert result["proxmox_vms"] == expected_output + assert len(result["proxmox_vms"]) == 1 + + def test_get_multiple_vms_with_the_same_name(self): + name = "test-lxc.home.arpa" + self.connect_mock.return_value.cluster.resources.get.return_value = [ + {"name": name, "vmid": "102"}, + {"name": name, "vmid": "104"}, + ] + + with pytest.raises(AnsibleExitJson) as exc_info: + expected_output = [vm for vm in EXPECTED_VMS_OUTPUT if vm["name"] == name] + set_module_args(get_module_args(type="all", name=name)) + self.module.main() + + result = exc_info.value.args[0] + assert result["proxmox_vms"] == expected_output + assert len(result["proxmox_vms"]) == 2 + + def test_get_vm_with_an_empty_name(self): + name = "" + self.connect_mock.return_value.cluster.resources.get.return_value = [ + {"name": name, "vmid": "105"}, + ] + + with pytest.raises(AnsibleExitJson) as exc_info: + expected_output = [vm for vm in EXPECTED_VMS_OUTPUT if vm["name"] == name] + set_module_args(get_module_args(type="all", name=name)) + self.module.main() + + result = exc_info.value.args[0] + assert result["proxmox_vms"] == expected_output + assert len(result["proxmox_vms"]) == 1 + + def test_get_all_lxc_vms_from_specific_node(self): + with pytest.raises(AnsibleExitJson) as exc_info: + expected_output = [ + vm + for vm in EXPECTED_VMS_OUTPUT + if vm["node"] == NODE1 and vm["type"] == "lxc" + ] + set_module_args(get_module_args(type="lxc", node=NODE1)) + self.module.main() + + result = exc_info.value.args[0] + assert result["proxmox_vms"] == expected_output + assert len(result["proxmox_vms"]) == 1 + + def test_get_all_qemu_vms_from_specific_node(self): + with pytest.raises(AnsibleExitJson) as exc_info: + expected_output = [ + vm + for vm in EXPECTED_VMS_OUTPUT + if vm["node"] == NODE1 and vm["type"] == "qemu" + ] + set_module_args(get_module_args(type="qemu", node=NODE1)) + self.module.main() + + result = exc_info.value.args[0] + assert result["proxmox_vms"] == expected_output + assert len(result["proxmox_vms"]) == 1 + + def test_get_all_vms_from_specific_node(self): + with pytest.raises(AnsibleExitJson) as exc_info: + expected_output = [vm for vm in EXPECTED_VMS_OUTPUT if vm["node"] == NODE1] + set_module_args(get_module_args(node=NODE1)) + self.module.main() + + result = exc_info.value.args[0] + assert result["proxmox_vms"] == expected_output + assert len(result["proxmox_vms"]) == 2 + + def test_module_returns_empty_list_when_vm_does_not_exist(self): + with pytest.raises(AnsibleExitJson) as exc_info: + vmid = 200 + set_module_args(get_module_args(type="all", vmid=vmid)) + self.module.main() + + result = exc_info.value.args[0] + assert result["proxmox_vms"] == [] + + def test_module_fail_when_qemu_request_fails(self): + self.connect_mock.return_value.nodes.return_value.qemu.return_value.get.side_effect = IOError( + "Some mocked connection error." + ) + with pytest.raises(AnsibleFailJson) as exc_info: + set_module_args(get_module_args(type="qemu")) + self.module.main() + + result = exc_info.value.args[0] + assert "Failed to retrieve QEMU VMs information:" in result["msg"] + + def test_module_fail_when_lxc_request_fails(self): + self.connect_mock.return_value.nodes.return_value.lxc.return_value.get.side_effect = IOError( + "Some mocked connection error." + ) + with pytest.raises(AnsibleFailJson) as exc_info: + set_module_args(get_module_args(type="lxc")) + self.module.main() + + result = exc_info.value.args[0] + assert "Failed to retrieve LXC VMs information:" in result["msg"] + + def test_module_fail_when_cluster_resources_request_fails(self): + self.connect_mock.return_value.cluster.return_value.resources.return_value.get.side_effect = IOError( + "Some mocked connection error." + ) + with pytest.raises(AnsibleFailJson) as exc_info: + set_module_args(get_module_args()) + self.module.main() + + result = exc_info.value.args[0] + assert ( + "Failed to retrieve VMs information from cluster resources:" + in result["msg"] + ) + + def test_module_fail_when_node_does_not_exist(self): + with pytest.raises(AnsibleFailJson) as exc_info: + set_module_args(get_module_args(type="all", node="NODE3")) + self.module.main() + + result = exc_info.value.args[0] + assert result["msg"] == "Node NODE3 doesn't exist in PVE cluster" + + def test_call_to_get_vmid_is_not_used_when_vmid_provided(self): + with patch( + "ansible_collections.community.general.plugins.module_utils.proxmox.ProxmoxAnsible.get_vmid" + ) as get_vmid_mock: + with pytest.raises(AnsibleExitJson): + vmid = 100 + set_module_args( + get_module_args(type="all", vmid=vmid, name="something") + ) + self.module.main() + + assert get_vmid_mock.call_count == 0 + + def test_config_returned_when_specified_qemu_vm_with_config(self): + config_vm_value = { + 'scsi0': 'local-lvm:vm-101-disk-0,iothread=1,size=32G', + 'net0': 'virtio=4E:79:9F:A8:EE:E4,bridge=vmbr0,firewall=1', + 'scsihw': 'virtio-scsi-single', + 'cores': 1, + 'name': 'test1', + 'ostype': 'l26', + 'boot': 'order=scsi0;ide2;net0', + 'memory': 2048, + 'sockets': 1, + } + (self.connect_mock.return_value.nodes.return_value.qemu.return_value. + config.return_value.get.return_value) = config_vm_value + + with pytest.raises(AnsibleExitJson) as exc_info: + vmid = 101 + set_module_args(get_module_args( + type="qemu", + vmid=vmid, + config="current", + )) + expected_output = [vm for vm in EXPECTED_VMS_OUTPUT if vm["vmid"] == vmid] + expected_output[0]["config"] = config_vm_value + self.module.main() + + result = exc_info.value.args[0] + assert result["proxmox_vms"] == expected_output diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_puppet.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_puppet.py index f62523e7f..57f88ada1 100644 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_puppet.py +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_puppet.py @@ -12,216 +12,9 @@ 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 +from .helper import Helper -import pytest -TESTED_MODULE = puppet.__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) - - -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_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"] +Helper.from_module(puppet, __name__) diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_puppet.yaml b/ansible_collections/community/general/tests/unit/plugins/modules/test_puppet.yaml new file mode 100644 index 000000000..308be9797 --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_puppet.yaml @@ -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: &env-def {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: *env-def + 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: *env-def + 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: *env-def + 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: *env-def + 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: *env-def + 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: *env-def + 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: *env-def + 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: *env-def + 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: *env-def + 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: *env-def + 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: *env-def + rc: 0 + out: "" + err: "" diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_redhat_subscription.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_redhat_subscription.py index 4bf272916..9473d0d46 100644 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_redhat_subscription.py +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_redhat_subscription.py @@ -22,13 +22,15 @@ def patch_redhat_subscription(mocker): """ Function used for mocking some parts of redhat_subscription module """ - mocker.patch('ansible_collections.community.general.plugins.modules.redhat_subscription.RegistrationBase.REDHAT_REPO') + mocker.patch('ansible_collections.community.general.plugins.modules.redhat_subscription.Rhsm.REDHAT_REPO') mocker.patch('ansible_collections.community.general.plugins.modules.redhat_subscription.isfile', return_value=False) mocker.patch('ansible_collections.community.general.plugins.modules.redhat_subscription.unlink', return_value=True) mocker.patch('ansible_collections.community.general.plugins.modules.redhat_subscription.AnsibleModule.get_bin_path', return_value='/testbin/subscription-manager') mocker.patch('ansible_collections.community.general.plugins.modules.redhat_subscription.Rhsm._can_connect_to_dbus', return_value=False) + mocker.patch('ansible_collections.community.general.plugins.modules.redhat_subscription.Rhsm._has_dbus_interface', + return_value=False) mocker.patch('ansible_collections.community.general.plugins.modules.redhat_subscription.getuid', return_value=0) diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_redis_info.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_redis_info.py index 8b30a2316..cdc78680e 100644 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_redis_info.py +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_redis_info.py @@ -50,7 +50,12 @@ class TestRedisInfoModule(ModuleTestCase): set_module_args({}) self.module.main() self.assertEqual(redis_client.call_count, 1) - self.assertEqual(redis_client.call_args, ({'host': 'localhost', 'port': 6379, 'password': None},)) + self.assertEqual(redis_client.call_args, ({'host': 'localhost', + 'port': 6379, + 'password': None, + 'ssl': False, + 'ssl_ca_certs': None, + 'ssl_cert_reqs': 'required'},)) self.assertEqual(result.exception.args[0]['info']['redis_version'], '999.999.999') def test_with_parameters(self): @@ -64,7 +69,34 @@ class TestRedisInfoModule(ModuleTestCase): }) self.module.main() self.assertEqual(redis_client.call_count, 1) - self.assertEqual(redis_client.call_args, ({'host': 'test', 'port': 1234, 'password': 'PASS'},)) + self.assertEqual(redis_client.call_args, ({'host': 'test', + 'port': 1234, + 'password': 'PASS', + 'ssl': False, + 'ssl_ca_certs': None, + 'ssl_cert_reqs': 'required'},)) + self.assertEqual(result.exception.args[0]['info']['redis_version'], '999.999.999') + + def test_with_tls_parameters(self): + """Test with tls parameters""" + with self.patch_redis_client(side_effect=FakeRedisClient) as redis_client: + with self.assertRaises(AnsibleExitJson) as result: + set_module_args({ + 'login_host': 'test', + 'login_port': 1234, + 'login_password': 'PASS', + 'tls': True, + 'ca_certs': '/etc/ssl/ca.pem', + 'validate_certs': False + }) + self.module.main() + self.assertEqual(redis_client.call_count, 1) + self.assertEqual(redis_client.call_args, ({'host': 'test', + 'port': 1234, + 'password': 'PASS', + 'ssl': True, + 'ssl_ca_certs': '/etc/ssl/ca.pem', + 'ssl_cert_reqs': None},)) self.assertEqual(result.exception.args[0]['info']['redis_version'], '999.999.999') def test_with_fail_client(self): diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_rhsm_release.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_rhsm_release.py index c5696962b..e8b2db6fd 100644 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_rhsm_release.py +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_rhsm_release.py @@ -14,6 +14,8 @@ from ansible_collections.community.general.tests.unit.plugins.modules.utils impo class RhsmRepositoryReleaseModuleTestCase(ModuleTestCase): module = rhsm_release + SUBMAN_KWARGS = dict(check_rc=True, expand_user_and_vars=False) + def setUp(self): super(RhsmRepositoryReleaseModuleTestCase, self).setUp() @@ -63,8 +65,8 @@ class RhsmRepositoryReleaseModuleTestCase(ModuleTestCase): self.assertTrue(result['changed']) self.assertEqual('7.5', result['current_release']) self.module_main_command.assert_has_calls([ - call('/testbin/subscription-manager release --show', check_rc=True), - call('/testbin/subscription-manager release --set 7.5', check_rc=True), + call(['/testbin/subscription-manager', 'release', '--show'], **self.SUBMAN_KWARGS), + call(['/testbin/subscription-manager', 'release', '--set', '7.5'], **self.SUBMAN_KWARGS), ]) def test_release_set_idempotent(self): @@ -81,7 +83,7 @@ class RhsmRepositoryReleaseModuleTestCase(ModuleTestCase): self.assertFalse(result['changed']) self.assertEqual('7.5', result['current_release']) self.module_main_command.assert_has_calls([ - call('/testbin/subscription-manager release --show', check_rc=True), + call(['/testbin/subscription-manager', 'release', '--show'], **self.SUBMAN_KWARGS), ]) def test_release_unset(self): @@ -100,8 +102,8 @@ class RhsmRepositoryReleaseModuleTestCase(ModuleTestCase): self.assertTrue(result['changed']) self.assertIsNone(result['current_release']) self.module_main_command.assert_has_calls([ - call('/testbin/subscription-manager release --show', check_rc=True), - call('/testbin/subscription-manager release --unset', check_rc=True), + call(['/testbin/subscription-manager', 'release', '--show'], **self.SUBMAN_KWARGS), + call(['/testbin/subscription-manager', 'release', '--unset'], **self.SUBMAN_KWARGS), ]) def test_release_unset_idempotent(self): @@ -118,7 +120,7 @@ class RhsmRepositoryReleaseModuleTestCase(ModuleTestCase): self.assertFalse(result['changed']) self.assertIsNone(result['current_release']) self.module_main_command.assert_has_calls([ - call('/testbin/subscription-manager release --show', check_rc=True), + call(['/testbin/subscription-manager', 'release', '--show'], **self.SUBMAN_KWARGS), ]) def test_release_insane(self): diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_rhsm_repository.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_rhsm_repository.py new file mode 100644 index 000000000..e822c7e84 --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_rhsm_repository.py @@ -0,0 +1,833 @@ +# -*- coding: utf-8 -*- +# Author: Pino Toscano (ptoscano@redhat.com) +# Largely adapted from test_rhsm_repository by +# Jiri Hnidek (jhnidek@redhat.com) +# +# Copyright (c) Pino Toscano (ptoscano@redhat.com) +# Copyright (c) Jiri Hnidek (jhnidek@redhat.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 + +import copy +import fnmatch +import itertools +import json + +from ansible.module_utils import basic +from ansible_collections.community.general.plugins.modules import rhsm_repository + +import pytest + +TESTED_MODULE = rhsm_repository.__name__ + + +@pytest.fixture +def patch_rhsm_repository(mocker): + """ + Function used for mocking some parts of rhsm_repository module + """ + mocker.patch('ansible_collections.community.general.plugins.modules.rhsm_repository.AnsibleModule.get_bin_path', + return_value='/testbin/subscription-manager') + mocker.patch('ansible_collections.community.general.plugins.modules.rhsm_repository.os.getuid', + return_value=0) + + +class Repos(object): + """ + Helper class to represent a list of repositories + + Each repository is an object with few properties. + """ + + _SUBMAN_OUT_HEADER = """+----------------------------------------------------------+ + Available Repositories in /etc/yum.repos.d/redhat.repo ++----------------------------------------------------------+ +""" + _SUBMAN_OUT_ENTRY = """Repo ID: %s +Repo Name: %s +Repo URL: %s +Enabled: %s + +""" + + def __init__(self, repos): + self.repos = repos + + def to_subman_list_output(self): + """ + Return a string mimicking the output of `subscription-manager repos --list` + """ + out = self._SUBMAN_OUT_HEADER + for repo in self.repos: + out += self._SUBMAN_OUT_ENTRY % ( + repo["id"], + repo["name"], + repo["url"], + "1" if repo["enabled"] else "0", + ) + + return out + + def copy(self): + """ + Clone the object; used to do changes (enable(), disable()) without + affecting the original object. + """ + return copy.deepcopy(self) + + def _set_status(self, repo_id, status): + for repo in self.repos: + if fnmatch.fnmatch(repo['id'], repo_id): + repo['enabled'] = status + + def enable(self, repo_ids): + """ + Enable the specified IDs. + + 'repo_ids' can be either a string or a list of strings representing + an ID (wildcard included). + + Returns the same object, so calls to this can be chained. + """ + if not isinstance(repo_ids, list): + repo_ids = [repo_ids] + for repo_id in repo_ids: + self._set_status(repo_id, True) + return self + + def disable(self, repo_ids): + """ + Disable the specified IDs. + + 'repo_ids' can be either a string or a list of strings representing + an ID (wildcard included). + + Returns the same object, so calls to this can be chained. + """ + if not isinstance(repo_ids, list): + repo_ids = [repo_ids] + for repo_id in repo_ids: + self._set_status(repo_id, False) + return self + + def _filter_by_status(self, filter, status): + return [ + repo['id'] + for repo in self.repos + if repo['enabled'] == status and fnmatch.fnmatch(repo['id'], filter) + ] + + def ids_enabled(self, filter='*'): + """ + Get a list with the enabled repositories. + + 'filter' is a wildcard expression. + """ + return self._filter_by_status(filter, True) + + def ids_disabled(self, filter='*'): + """ + Get a list with the disabled repositories. + + 'filter' is a wildcard expression. + """ + return self._filter_by_status(filter, False) + + def to_list(self): + """ + Get the list of repositories. + """ + return self.repos + + +def flatten(iter_of_iters): + return list(itertools.chain.from_iterable(iter_of_iters)) + + +# List with test repositories, directly from the Candlepin test data. +REPOS_LIST = [ + { + "id": "never-enabled-content-801", + "name": "never-enabled-content-801", + "url": "https://candlepin.local/foo/path/never_enabled/801-100", + "enabled": False, + }, + { + "id": "never-enabled-content-100000000000060", + "name": "never-enabled-content-100000000000060", + "url": "https://candlepin.local/foo/path/never_enabled/100000000000060-100", + "enabled": False, + }, + { + "id": "awesomeos-x86_64-1000000000000023", + "name": "awesomeos-x86_64-1000000000000023", + "url": "https://candlepin.local/path/to/awesomeos/x86_64/1000000000000023-11124", + "enabled": False, + }, + { + "id": "awesomeos-ppc64-100000000000011", + "name": "awesomeos-ppc64-100000000000011", + "url": "https://candlepin.local/path/to/awesomeos/ppc64/100000000000011-11126", + "enabled": False, + }, + { + "id": "awesomeos-99000", + "name": "awesomeos-99000", + "url": "https://candlepin.local/path/to/generic/awesomeos/99000-11113", + "enabled": True, + }, + { + "id": "content-label-27060", + "name": "content-27060", + "url": "https://candlepin.local/foo/path/common/27060-1111", + "enabled": True, + }, + { + "id": "content-label-no-gpg-32060", + "name": "content-nogpg-32060", + "url": "https://candlepin.local/foo/path/no_gpg/32060-234", + "enabled": False, + }, + { + "id": "awesomeos-1000000000000023", + "name": "awesomeos-1000000000000023", + "url": "https://candlepin.local/path/to/generic/awesomeos/1000000000000023-11113", + "enabled": False, + }, + { + "id": "awesomeos-x86-100000000000020", + "name": "awesomeos-x86-100000000000020", + "url": "https://candlepin.local/path/to/awesomeos/x86/100000000000020-11120", + "enabled": False, + }, + { + "id": "awesomeos-x86_64-99000", + "name": "awesomeos-x86_64-99000", + "url": "https://candlepin.local/path/to/awesomeos/x86_64/99000-11124", + "enabled": True, + }, + { + "id": "awesomeos-s390x-99000", + "name": "awesomeos-s390x-99000", + "url": "https://candlepin.local/path/to/awesomeos/s390x/99000-11121", + "enabled": False, + }, + { + "id": "awesomeos-modifier-37080", + "name": "awesomeos-modifier-37080", + "url": "https://candlepin.local/example.com/awesomeos-modifier/37080-1112", + "enabled": False, + }, + { + "id": "awesomeos-i686-99000", + "name": "awesomeos-i686-99000", + "url": "https://candlepin.local/path/to/awesomeos/i686/99000-11123", + "enabled": False, + }, + { + "id": "fake-content-38072", + "name": "fake-content-38072", + "url": "https://candlepin.local/path/to/fake-content/38072-3902", + "enabled": True, + }, +] + + +# A static object with the list of repositories, used as reference to query +# the repositories, and create (by copy()) new Repos objects. +REPOS = Repos(REPOS_LIST) + +# The mock string for the output of `subscription-manager repos --list`. +REPOS_LIST_OUTPUT = REPOS.to_subman_list_output() + +# MUST match what's in the Rhsm class in the module. +SUBMAN_KWARGS = { + 'environ_update': dict(LANG='C', LC_ALL='C', LC_MESSAGES='C'), + 'expand_user_and_vars': False, + 'use_unsafe_shell': False, +} + + +TEST_CASES = [ + # enable a disabled repository + [ + { + 'name': 'awesomeos-1000000000000023', + }, + { + 'id': 'test_enable_single', + 'run_command.calls': [ + ( + [ + '/testbin/subscription-manager', + 'repos', + '--list', + ], + SUBMAN_KWARGS, + (0, REPOS_LIST_OUTPUT, '') + ), + ( + [ + '/testbin/subscription-manager', + 'repos', + '--enable', + 'awesomeos-1000000000000023', + ], + SUBMAN_KWARGS, + (0, '', '') + ), + ], + 'changed': True, + 'repositories': REPOS.copy().enable('awesomeos-1000000000000023'), + } + ], + # enable an already enabled repository + [ + { + 'name': 'fake-content-38072', + }, + { + 'id': 'test_enable_already_enabled', + 'run_command.calls': [ + ( + [ + '/testbin/subscription-manager', + 'repos', + '--list', + ], + SUBMAN_KWARGS, + (0, REPOS_LIST_OUTPUT, '') + ), + ], + 'changed': False, + 'repositories': REPOS.copy(), + } + ], + # enable two disabled repositories + [ + { + 'name': ['awesomeos-1000000000000023', 'content-label-no-gpg-32060'], + }, + { + 'id': 'test_enable_multiple', + 'run_command.calls': [ + ( + [ + '/testbin/subscription-manager', + 'repos', + '--list', + ], + SUBMAN_KWARGS, + (0, REPOS_LIST_OUTPUT, '') + ), + ( + [ + '/testbin/subscription-manager', + 'repos', + '--enable', + 'awesomeos-1000000000000023', + '--enable', + 'content-label-no-gpg-32060', + ], + SUBMAN_KWARGS, + (0, '', '') + ), + ], + 'changed': True, + 'repositories': REPOS.copy().enable('awesomeos-1000000000000023').enable('content-label-no-gpg-32060'), + } + ], + # enable two repositories, one disabled and one already enabled + [ + { + 'name': ['awesomeos-1000000000000023', 'fake-content-38072'], + }, + { + 'id': 'test_enable_multiple_mixed', + 'run_command.calls': [ + ( + [ + '/testbin/subscription-manager', + 'repos', + '--list', + ], + SUBMAN_KWARGS, + (0, REPOS_LIST_OUTPUT, '') + ), + ( + [ + '/testbin/subscription-manager', + 'repos', + '--enable', + 'awesomeos-1000000000000023', + '--enable', + 'fake-content-38072', + ], + SUBMAN_KWARGS, + (0, '', '') + ), + ], + 'changed': True, + 'repositories': REPOS.copy().enable('awesomeos-1000000000000023'), + } + ], + # purge everything but never-enabled-content-801 (disabled) + [ + { + 'name': 'never-enabled-content-801', + 'purge': True, + }, + { + 'id': 'test_purge_everything_but_one_disabled', + 'run_command.calls': [ + ( + [ + '/testbin/subscription-manager', + 'repos', + '--list', + ], + SUBMAN_KWARGS, + (0, REPOS_LIST_OUTPUT, '') + ), + ( + [ + '/testbin/subscription-manager', + 'repos', + '--enable', + 'never-enabled-content-801', + ] + flatten([['--disable', i] for i in REPOS.ids_enabled() if i != 'never-enabled-content-801']), + SUBMAN_KWARGS, + (0, '', '') + ), + ], + 'changed': True, + 'repositories': REPOS.copy().disable('*').enable('never-enabled-content-801'), + } + ], + # purge everything but awesomeos-99000 (already enabled) + [ + { + 'name': 'awesomeos-99000', + 'purge': True, + }, + { + 'id': 'test_purge_everything_but_one_enabled', + 'run_command.calls': [ + ( + [ + '/testbin/subscription-manager', + 'repos', + '--list', + ], + SUBMAN_KWARGS, + (0, REPOS_LIST_OUTPUT, '') + ), + ( + [ + '/testbin/subscription-manager', + 'repos', + '--enable', + 'awesomeos-99000', + '--disable', + 'content-label-27060', + '--disable', + 'awesomeos-x86_64-99000', + '--disable', + 'fake-content-38072', + ], + SUBMAN_KWARGS, + (0, '', '') + ), + ], + 'changed': True, + 'repositories': REPOS.copy().disable('*').enable('awesomeos-99000'), + } + ], + # enable everything, then purge everything but content-label-27060 + [ + { + 'name': 'content-label-27060', + 'purge': True, + }, + { + 'id': 'test_enable_everything_purge_everything_but_one_enabled', + 'run_command.calls': [ + ( + [ + '/testbin/subscription-manager', + 'repos', + '--list', + ], + SUBMAN_KWARGS, + (0, REPOS.copy().enable('*').to_subman_list_output(), '') + ), + ( + [ + '/testbin/subscription-manager', + 'repos', + '--enable', + 'content-label-27060', + '--disable', + 'never-enabled-content-801', + '--disable', + 'never-enabled-content-100000000000060', + '--disable', + 'awesomeos-x86_64-1000000000000023', + '--disable', + 'awesomeos-ppc64-100000000000011', + '--disable', + 'awesomeos-99000', + '--disable', + 'content-label-no-gpg-32060', + '--disable', + 'awesomeos-1000000000000023', + '--disable', + 'awesomeos-x86-100000000000020', + '--disable', + 'awesomeos-x86_64-99000', + '--disable', + 'awesomeos-s390x-99000', + '--disable', + 'awesomeos-modifier-37080', + '--disable', + 'awesomeos-i686-99000', + '--disable', + 'fake-content-38072', + ], + SUBMAN_KWARGS, + (0, '', '') + ), + ], + 'changed': True, + 'repositories': REPOS.copy().disable('*').enable('content-label-27060'), + } + ], + # enable all awesomeos-* + [ + { + 'name': 'awesomeos-*', + }, + { + 'id': 'test_enable_all_awesomeos_star', + 'run_command.calls': [ + ( + [ + '/testbin/subscription-manager', + 'repos', + '--list', + ], + SUBMAN_KWARGS, + (0, REPOS_LIST_OUTPUT, '') + ), + ( + [ + '/testbin/subscription-manager', + 'repos', + '--enable', + 'awesomeos-x86_64-1000000000000023', + '--enable', + 'awesomeos-ppc64-100000000000011', + '--enable', + 'awesomeos-99000', + '--enable', + 'awesomeos-1000000000000023', + '--enable', + 'awesomeos-x86-100000000000020', + '--enable', + 'awesomeos-x86_64-99000', + '--enable', + 'awesomeos-s390x-99000', + '--enable', + 'awesomeos-modifier-37080', + '--enable', + 'awesomeos-i686-99000', + ], + SUBMAN_KWARGS, + (0, '', '') + ), + ], + 'changed': True, + 'repositories': REPOS.copy().enable('awesomeos-*'), + } + ], + # purge everything but awesomeos-* + [ + { + 'name': REPOS.ids_enabled('awesomeos-*'), + 'purge': True, + }, + { + 'id': 'test_purge_everything_but_awesomeos_list', + 'run_command.calls': [ + ( + [ + '/testbin/subscription-manager', + 'repos', + '--list', + ], + SUBMAN_KWARGS, + (0, REPOS_LIST_OUTPUT, '') + ), + ( + [ + '/testbin/subscription-manager', + 'repos', + '--enable', + 'awesomeos-99000', + '--enable', + 'awesomeos-x86_64-99000', + '--disable', + 'content-label-27060', + '--disable', + 'fake-content-38072', + ], + SUBMAN_KWARGS, + (0, '', '') + ), + ], + 'changed': True, + 'repositories': REPOS.copy().disable('*').enable(REPOS.ids_enabled('awesomeos-*')), + } + ], + # enable a repository that does not exist + [ + { + 'name': 'repo-that-does-not-exist', + }, + { + 'id': 'test_enable_nonexisting', + 'run_command.calls': [ + ( + [ + '/testbin/subscription-manager', + 'repos', + '--list', + ], + SUBMAN_KWARGS, + (0, REPOS_LIST_OUTPUT, '') + ), + ], + 'failed': True, + 'msg': 'repo-that-does-not-exist is not a valid repository ID', + } + ], + # disable an enabled repository + [ + { + 'name': 'awesomeos-99000', + 'state': 'disabled', + }, + { + 'id': 'test_disable_single', + 'run_command.calls': [ + ( + [ + '/testbin/subscription-manager', + 'repos', + '--list', + ], + SUBMAN_KWARGS, + (0, REPOS_LIST_OUTPUT, '') + ), + ( + [ + '/testbin/subscription-manager', + 'repos', + '--disable', + 'awesomeos-99000', + ], + SUBMAN_KWARGS, + (0, '', '') + ), + ], + 'changed': True, + 'repositories': REPOS.copy().disable('awesomeos-99000'), + } + ], + # disable an enabled repository (using state=absent) + [ + { + 'name': 'awesomeos-99000', + 'state': 'absent', + }, + { + 'id': 'test_disable_single_using_absent', + 'run_command.calls': [ + ( + [ + '/testbin/subscription-manager', + 'repos', + '--list', + ], + SUBMAN_KWARGS, + (0, REPOS_LIST_OUTPUT, '') + ), + ( + [ + '/testbin/subscription-manager', + 'repos', + '--disable', + 'awesomeos-99000', + ], + SUBMAN_KWARGS, + (0, '', '') + ), + ], + 'changed': True, + 'repositories': REPOS.copy().disable('awesomeos-99000'), + } + ], + # disable an already disabled repository + [ + { + 'name': 'never-enabled-content-801', + 'state': 'disabled', + }, + { + 'id': 'test_disable_already_disabled', + 'run_command.calls': [ + ( + [ + '/testbin/subscription-manager', + 'repos', + '--list', + ], + SUBMAN_KWARGS, + (0, REPOS_LIST_OUTPUT, '') + ), + ], + 'changed': False, + 'repositories': REPOS.copy(), + } + ], + # disable an already disabled repository, and purge + [ + { + 'name': 'never-enabled-content-801', + 'state': 'disabled', + 'purge': True, + }, + { + 'id': 'test_disable_already_disabled_and_purge', + 'run_command.calls': [ + ( + [ + '/testbin/subscription-manager', + 'repos', + '--list', + ], + SUBMAN_KWARGS, + (0, REPOS_LIST_OUTPUT, '') + ), + ( + [ + '/testbin/subscription-manager', + 'repos', + ] + flatten([['--disable', i] for i in REPOS.ids_enabled()]), + SUBMAN_KWARGS, + (0, '', '') + ), + ], + 'changed': True, + 'repositories': REPOS.copy().disable('*'), + } + ], + # disable an enabled repository, and purge + [ + { + 'name': 'awesomeos-99000', + 'state': 'disabled', + 'purge': True, + }, + { + 'id': 'test_disable_single_and_purge', + 'run_command.calls': [ + ( + [ + '/testbin/subscription-manager', + 'repos', + '--list', + ], + SUBMAN_KWARGS, + (0, REPOS_LIST_OUTPUT, '') + ), + ( + [ + '/testbin/subscription-manager', + 'repos', + ] + flatten([['--disable', i] for i in REPOS.ids_enabled()]), + SUBMAN_KWARGS, + (0, '', '') + ), + ], + 'changed': True, + 'repositories': REPOS.copy().disable('*'), + } + ], + # disable a repository that does not exist + [ + { + 'name': 'repo-that-does-not-exist', + 'state': 'disabled', + }, + { + 'id': 'test_disable_nonexisting', + 'run_command.calls': [ + ( + [ + '/testbin/subscription-manager', + 'repos', + '--list', + ], + SUBMAN_KWARGS, + (0, REPOS_LIST_OUTPUT, '') + ), + ], + 'failed': True, + 'msg': 'repo-that-does-not-exist is not a valid repository ID', + } + ], +] + + +TEST_CASES_IDS = [item[1]['id'] for item in TEST_CASES] + + +@pytest.mark.parametrize('patch_ansible_module, testcase', TEST_CASES, ids=TEST_CASES_IDS, indirect=['patch_ansible_module']) +@pytest.mark.usefixtures('patch_ansible_module') +def test_rhsm_repository(mocker, capfd, patch_rhsm_repository, testcase): + """ + Run unit tests for test cases listen 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.object( + basic.AnsibleModule, + 'run_command', + side_effect=call_results) + + # Try to run test case + with pytest.raises(SystemExit): + rhsm_repository.main() + + out, err = capfd.readouterr() + results = json.loads(out) + + if 'failed' in testcase: + assert results['failed'] == testcase['failed'] + assert results['msg'] == testcase['msg'] + else: + assert 'changed' in results + assert results['changed'] == testcase['changed'] + assert results['repositories'] == testcase['repositories'].to_list() + + assert basic.AnsibleModule.run_command.call_count == len(testcase['run_command.calls']) + # FIXME ideally we need also to compare the actual calls with the expected + # ones; the problem is that the module uses a dict to collect the repositories + # to enable and disable, so the order of the --enable/--disable parameters to + # `subscription-manager repos` is not stable diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_sap_task_list_execute.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_sap_task_list_execute.py deleted file mode 100644 index 34c97c4a8..000000000 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_sap_task_list_execute.py +++ /dev/null @@ -1,91 +0,0 @@ -# Copyright (c) Ansible project -# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) -# SPDX-License-Identifier: GPL-3.0-or-later - -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -import sys -from ansible_collections.community.general.tests.unit.compat.mock import patch, MagicMock -from ansible_collections.community.general.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args - -sys.modules['pyrfc'] = MagicMock() -sys.modules['pyrfc.Connection'] = MagicMock() -sys.modules['xmltodict'] = MagicMock() -sys.modules['xmltodict.parse'] = MagicMock() - -from ansible_collections.community.general.plugins.modules import sap_task_list_execute - - -class TestSAPRfcModule(ModuleTestCase): - - def setUp(self): - super(TestSAPRfcModule, self).setUp() - self.module = sap_task_list_execute - - def tearDown(self): - super(TestSAPRfcModule, self).tearDown() - - def define_rfc_connect(self, mocker): - return mocker.patch(self.module.call_rfc_method) - - def test_without_required_parameters(self): - """Failure must occurs when all parameters are missing""" - with self.assertRaises(AnsibleFailJson): - set_module_args({}) - self.module.main() - - def test_error_no_task_list(self): - """tests fail to exec task list""" - - set_module_args({ - "conn_username": "DDIC", - "conn_password": "Test1234", - "host": "10.1.8.9", - "task_to_execute": "SAP_BASIS_SSL_CHECK" - }) - - with patch.object(self.module, 'Connection') as conn: - conn.return_value = '' - with self.assertRaises(AnsibleFailJson) as result: - self.module.main() - self.assertEqual(result.exception.args[0]['msg'], 'The task list does not exsist.') - - def test_success(self): - """test execute task list success""" - - set_module_args({ - "conn_username": "DDIC", - "conn_password": "Test1234", - "host": "10.1.8.9", - "task_to_execute": "SAP_BASIS_SSL_CHECK" - }) - with patch.object(self.module, 'xml_to_dict') as XML: - XML.return_value = {'item': [{'TASK': {'CHECK_STATUS_DESCR': 'Check successfully', - 'STATUS_DESCR': 'Executed successfully', 'TASKNAME': 'CL_STCT_CHECK_SEC_CRYPTO', - 'LNR': '1', 'DESCRIPTION': 'Check SAP Cryptographic Library', 'DOCU_EXIST': 'X', - 'LOG_EXIST': 'X', 'ACTION_SKIP': None, 'ACTION_UNSKIP': None, 'ACTION_CONFIRM': None, - 'ACTION_MAINTAIN': None}}]} - - with self.assertRaises(AnsibleExitJson) as result: - sap_task_list_execute.main() - self.assertEqual(result.exception.args[0]['out'], {'item': [{'TASK': {'CHECK_STATUS_DESCR': 'Check successfully', - 'STATUS_DESCR': 'Executed successfully', 'TASKNAME': 'CL_STCT_CHECK_SEC_CRYPTO', - 'LNR': '1', 'DESCRIPTION': 'Check SAP Cryptographic Library', 'DOCU_EXIST': 'X', - 'LOG_EXIST': 'X', 'ACTION_SKIP': None, 'ACTION_UNSKIP': None, - 'ACTION_CONFIRM': None, 'ACTION_MAINTAIN': None}}]}) - - def test_success_no_log(self): - """test execute task list success without logs""" - - set_module_args({ - "conn_username": "DDIC", - "conn_password": "Test1234", - "host": "10.1.8.9", - "task_to_execute": "SAP_BASIS_SSL_CHECK" - }) - with patch.object(self.module, 'xml_to_dict') as XML: - XML.return_value = "No logs available." - with self.assertRaises(AnsibleExitJson) as result: - sap_task_list_execute.main() - self.assertEqual(result.exception.args[0]['out'], 'No logs available.') diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_sapcar_extract.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_sapcar_extract.py deleted file mode 100644 index bec9cf886..000000000 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_sapcar_extract.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2021, Rainer Leber (@rainerleber) <rainerleber@gmail.com> -# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) -# SPDX-License-Identifier: GPL-3.0-or-later - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - -from ansible_collections.community.general.plugins.modules import sapcar_extract -from ansible_collections.community.general.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args -from ansible_collections.community.general.tests.unit.compat.mock import patch -from ansible.module_utils import basic - - -def get_bin_path(*args, **kwargs): - """Function to return path of SAPCAR""" - return "/tmp/sapcar" - - -class Testsapcar_extract(ModuleTestCase): - """Main class for testing sapcar_extract module.""" - - def setUp(self): - """Setup.""" - super(Testsapcar_extract, self).setUp() - self.module = sapcar_extract - self.mock_get_bin_path = patch.object(basic.AnsibleModule, 'get_bin_path', get_bin_path) - self.mock_get_bin_path.start() - self.addCleanup(self.mock_get_bin_path.stop) # ensure that the patching is 'undone' - - def tearDown(self): - """Teardown.""" - super(Testsapcar_extract, self).tearDown() - - def test_without_required_parameters(self): - """Failure must occurs when all parameters are missing.""" - with self.assertRaises(AnsibleFailJson): - set_module_args({}) - self.module.main() - - def test_sapcar_extract(self): - """Check that result is changed.""" - set_module_args({ - 'path': "/tmp/HANA_CLIENT_REV2_00_053_00_LINUX_X86_64.SAR", - 'dest': "/tmp/test2", - 'binary_path': "/tmp/sapcar" - }) - with patch.object(basic.AnsibleModule, 'run_command') as run_command: - run_command.return_value = 0, '', '' # successful execution, no output - with self.assertRaises(AnsibleExitJson) as result: - sapcar_extract.main() - self.assertTrue(result.exception.args[0]['changed']) - self.assertEqual(run_command.call_count, 1) diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_simpleinit_msb.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_simpleinit_msb.py new file mode 100644 index 000000000..d97e9b5f2 --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_simpleinit_msb.py @@ -0,0 +1,200 @@ +# Copyright (c) 2023 Vlad Glagolev <scm@vaygr.net> +# 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.mock import patch +from ansible_collections.community.general.tests.unit.plugins.modules.utils import AnsibleFailJson, ModuleTestCase, set_module_args + +from ansible_collections.community.general.plugins.modules.simpleinit_msb import SimpleinitMSB, build_module + + +_TELINIT_LIST = """ +RUNLEVEL SCRIPT +2 smgl-suspend-single +3 crond +3 fuse +3 network +3 nscd +3 smgl-default-remote-fs +3 smgl-misc +3 sshd +DEV coldplug +DEV devices +DEV udevd +S hostname.sh +S hwclock.sh +S keymap.sh +S modutils +S mountall.sh +S mountroot.sh +S single +S smgl-default-crypt-fs +S smgl-metalog +S smgl-sysctl +S sysstat +""" + +_TELINIT_LIST_ENABLED = """ +smgl-suspend-single +crond +fuse +network +nscd +smgl-default-remote-fs +smgl-misc +sshd +coldplug +devices +udevd +hostname.sh +hwclock.sh +keymap.sh +modutils +mountall.sh +mountroot.sh +single +smgl-default-crypt-fs +smgl-metalog +smgl-sysctl +""" + +_TELINIT_LIST_DISABLED = """ +sysstat +""" + +_TELINIT_ALREADY_ENABLED = """ +Service smgl-suspend-single already enabled. +""" + +_TELINIT_ALREADY_DISABLED = """ +Service smgl-suspend-single already disabled. +""" + +_TELINIT_STATUS_RUNNING = """ +sshd is running with Process ID(s) 8510 8508 2195 +""" + +_TELINIT_STATUS_RUNNING_NOT = """ +/sbin/metalog is not running +""" + + +class TestSimpleinitMSB(ModuleTestCase): + + def setUp(self): + super(TestSimpleinitMSB, self).setUp() + + def tearDown(self): + super(TestSimpleinitMSB, self).tearDown() + + def init_module(self, args): + set_module_args(args) + + return SimpleinitMSB(build_module()) + + @patch('os.path.exists', return_value=True) + @patch('ansible.module_utils.basic.AnsibleModule.get_bin_path', return_value="/sbin/telinit") + def test_get_service_tools(self, *args, **kwargs): + simpleinit_msb = self.init_module({ + 'name': 'smgl-suspend-single', + 'state': 'running', + }) + + simpleinit_msb.get_service_tools() + + self.assertEqual(simpleinit_msb.telinit_cmd, "/sbin/telinit") + + @patch('ansible_collections.community.general.plugins.modules.simpleinit_msb.SimpleinitMSB.execute_command') + def test_service_exists(self, execute_command): + simpleinit_msb = self.init_module({ + 'name': 'smgl-suspend-single', + 'state': 'running', + }) + + execute_command.return_value = (0, _TELINIT_LIST, "") + + simpleinit_msb.service_exists() + + @patch('ansible_collections.community.general.plugins.modules.simpleinit_msb.SimpleinitMSB.execute_command') + def test_service_exists_not(self, execute_command): + simpleinit_msb = self.init_module({ + 'name': 'ntp', + 'state': 'running', + }) + + execute_command.return_value = (0, _TELINIT_LIST, "") + + with self.assertRaises(AnsibleFailJson) as context: + simpleinit_msb.service_exists() + + self.assertEqual("telinit could not find the requested service: ntp", context.exception.args[0]["msg"]) + + @patch('ansible_collections.community.general.plugins.modules.simpleinit_msb.SimpleinitMSB.service_exists') + @patch('ansible_collections.community.general.plugins.modules.simpleinit_msb.SimpleinitMSB.execute_command') + def test_check_service_enabled(self, execute_command, service_exists): + simpleinit_msb = self.init_module({ + 'name': 'nscd', + 'state': 'running', + 'enabled': 'true', + }) + + service_exists.return_value = True + execute_command.return_value = (0, _TELINIT_LIST_ENABLED, "") + + self.assertTrue(simpleinit_msb.service_enabled()) + + # Race condition check + with patch('ansible_collections.community.general.plugins.modules.simpleinit_msb.SimpleinitMSB.service_enabled', return_value=False): + execute_command.return_value = (0, "", _TELINIT_ALREADY_ENABLED) + + simpleinit_msb.service_enable() + + self.assertFalse(simpleinit_msb.changed) + + @patch('ansible_collections.community.general.plugins.modules.simpleinit_msb.SimpleinitMSB.service_exists') + @patch('ansible_collections.community.general.plugins.modules.simpleinit_msb.SimpleinitMSB.execute_command') + def test_check_service_disabled(self, execute_command, service_exists): + simpleinit_msb = self.init_module({ + 'name': 'sysstat', + 'state': 'stopped', + 'enabled': 'false', + }) + + service_exists.return_value = True + execute_command.return_value = (0, _TELINIT_LIST_DISABLED, "") + + self.assertFalse(simpleinit_msb.service_enabled()) + + # Race condition check + with patch('ansible_collections.community.general.plugins.modules.simpleinit_msb.SimpleinitMSB.service_enabled', return_value=True): + execute_command.return_value = (0, "", _TELINIT_ALREADY_DISABLED) + + simpleinit_msb.service_enable() + + self.assertFalse(simpleinit_msb.changed) + + @patch('ansible_collections.community.general.plugins.modules.simpleinit_msb.SimpleinitMSB.service_control') + def test_check_service_running(self, service_control): + simpleinit_msb = self.init_module({ + 'name': 'sshd', + 'state': 'running', + }) + + service_control.return_value = (0, _TELINIT_STATUS_RUNNING, "") + + self.assertFalse(simpleinit_msb.get_service_status()) + + @patch('ansible_collections.community.general.plugins.modules.simpleinit_msb.SimpleinitMSB.service_control') + def test_check_service_running_not(self, service_control): + simpleinit_msb = self.init_module({ + 'name': 'smgl-metalog', + 'state': 'running', + }) + + service_control.return_value = (0, _TELINIT_STATUS_RUNNING_NOT, "") + + self.assertFalse(simpleinit_msb.get_service_status()) diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_slack.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_slack.py index ab4405baa..52ac9b7f3 100644 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_slack.py +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_slack.py @@ -105,7 +105,7 @@ class TestSlackModule(ModuleTestCase): self.module.main() self.assertTrue(fetch_url_mock.call_count, 1) - self.assertEquals(fetch_url_mock.call_args[1]['url'], "https://slack.com/api/chat.postMessage") + self.assertEqual(fetch_url_mock.call_args[1]['url'], "https://slack.com/api/chat.postMessage") def test_edit_message(self): set_module_args({ @@ -125,9 +125,9 @@ class TestSlackModule(ModuleTestCase): self.module.main() self.assertTrue(fetch_url_mock.call_count, 2) - self.assertEquals(fetch_url_mock.call_args[1]['url'], "https://slack.com/api/chat.update") + self.assertEqual(fetch_url_mock.call_args[1]['url'], "https://slack.com/api/chat.update") call_data = json.loads(fetch_url_mock.call_args[1]['data']) - self.assertEquals(call_data['ts'], "12345") + self.assertEqual(call_data['ts'], "12345") def test_message_with_blocks(self): """tests sending a message with blocks""" diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_snap.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_snap.py new file mode 100644 index 000000000..480f637b6 --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_snap.py @@ -0,0 +1,474 @@ +# -*- 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 + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from .helper import Helper, ModuleTestCase, RunCmdCall +from ansible_collections.community.general.plugins.modules import snap + + +issue_6803_status_out = """Name Version Rev Tracking Publisher Notes +core20 20220826 1623 latest/stable canonical** base +lxd 5.6-794016a 23680 latest/stable/… canonical** - +snapd 2.57.4 17336 latest/stable canonical** snapd +""" + +issue_6803_microk8s_out = ( + "\rEnsure prerequisites for \"microk8s\" are available /" + "\rDownload snap \"microk8s\" (5372) from channel \"1.27/stable\" " + "\rDownload snap \"microk8s\" (5372) from channel \"1.27/stable\" \\" + "\rDownload snap \"microk8s\" (5372) from channel \"1.27/stable\" " + "\rDownload snap \"microk8s\" (5372) from channel \"1.27/stable\" /\u001b[?25" + "\r\u001b[7m\u001b[0mDownload snap \"microk8s\" (5372) from channel \"1.27/stable\" 0% 0B/s ages" + "\r\u001b[7m\u001b[0mDownload snap \"microk8s\" (5372) from channel \"1.27/stable\" 0% 0B/s ages" + "\r\u001b[7m\u001b[0mDownload snap \"microk8s\" (5372) from channel \"1.27/stable\" 0% 0B/s ages" + "\r\u001b[7m\u001b[0mDownload snap \"microk8s\" (5372) from channel \"1.27/stable\" 0% 880kB/s 3m21" + "\r\u001b[7m\u001b[0mDownload snap \"microk8s\" (5372) from channel \"1.27/stable\" 1% 2.82MB/s 1m02" + "\r\u001b[7mD\u001b[0mownload snap \"microk8s\" (5372) from channel \"1.27/stable\" 2% 4.71MB/s 37.0" + "\r\u001b[7mDo\u001b[0mwnload snap \"microk8s\" (5372) from channel \"1.27/stable\" 4% 9.09MB/s 18.8" + "\r\u001b[7mDown\u001b[0mload snap \"microk8s\" (5372) from channel \"1.27/stable\" 6% 12.4MB/s 13.5" + "\r\u001b[7mDownl\u001b[0moad snap \"microk8s\" (5372) from channel \"1.27/stable\" 7% 14.5MB/s 11.3" + "\r\u001b[7mDownloa\u001b[0md snap \"microk8s\" (5372) from channel \"1.27/stable\" 9% 15.9MB/s 10.1" + "\r\u001b[7mDownload \u001b[0msnap \"microk8s\" (5372) from channel \"1.27/stable\" 11% 18.0MB/s 8.75" + "\r\u001b[7mDownload s\u001b[0mnap \"microk8s\" (5372) from channel \"1.27/stable\" 13% 19.4MB/s 7.91" + "\r\u001b[7mDownload sn\u001b[0map \"microk8s\" (5372) from channel \"1.27/stable\" 15% 20.1MB/s 7.50" + "\r\u001b[7mDownload snap\u001b[0m \"microk8s\" (5372) from channel \"1.27/stable\" 17% 20.9MB/s 7.05" + "\r\u001b[7mDownload snap \"\u001b[0mmicrok8s\" (5372) from channel \"1.27/stable\" 19% 22.1MB/s 6.50" + "\r\u001b[7mDownload snap \"m\u001b[0microk8s\" (5372) from channel \"1.27/stable\" 21% 22.9MB/s 6.11" + "\r\u001b[7mDownload snap \"mic\u001b[0mrok8s\" (5372) from channel \"1.27/stable\" 23% 23.2MB/s 5.90" + "\r\u001b[7mDownload snap \"micr\u001b[0mok8s\" (5372) from channel \"1.27/stable\" 25% 23.9MB/s 5.58" + "\r\u001b[7mDownload snap \"microk\u001b[0m8s\" (5372) from channel \"1.27/stable\" 27% 24.5MB/s 5.30" + "\r\u001b[7mDownload snap \"microk8\u001b[0ms\" (5372) from channel \"1.27/stable\" 29% 24.9MB/s 5.09" + "\r\u001b[7mDownload snap \"microk8s\"\u001b[0m (5372) from channel \"1.27/stable\" 31% 25.4MB/s 4.85" + "\r\u001b[7mDownload snap \"microk8s\" (\u001b[0m5372) from channel \"1.27/stable\" 33% 25.8MB/s 4.63" + "\r\u001b[7mDownload snap \"microk8s\" (5\u001b[0m372) from channel \"1.27/stable\" 35% 26.2MB/s 4.42" + "\r\u001b[7mDownload snap \"microk8s\" (53\u001b[0m72) from channel \"1.27/stable\" 36% 26.3MB/s 4.30" + "\r\u001b[7mDownload snap \"microk8s\" (5372\u001b[0m) from channel \"1.27/stable\" 38% 26.7MB/s 4.10" + "\r\u001b[7mDownload snap \"microk8s\" (5372) \u001b[0mfrom channel \"1.27/stable\" 40% 26.9MB/s 3.95" + "\r\u001b[7mDownload snap \"microk8s\" (5372) f\u001b[0mrom channel \"1.27/stable\" 42% 27.2MB/s 3.77" + "\r\u001b[7mDownload snap \"microk8s\" (5372) fro\u001b[0mm channel \"1.27/stable\" 44% 27.4MB/s 3.63" + "\r\u001b[7mDownload snap \"microk8s\" (5372) from\u001b[0m channel \"1.27/stable\" 46% 27.8MB/s 3.44" + "\r\u001b[7mDownload snap \"microk8s\" (5372) from c\u001b[0mhannel \"1.27/stable\" 48% 27.9MB/s 3.31" + "\r\u001b[7mDownload snap \"microk8s\" (5372) from cha\u001b[0mnnel \"1.27/stable\" 50% 28.1MB/s 3.15" + "\r\u001b[7mDownload snap \"microk8s\" (5372) from chan\u001b[0mnel \"1.27/stable\" 52% 28.3MB/s 3.02" + "\r\u001b[7mDownload snap \"microk8s\" (5372) from channe\u001b[0ml \"1.27/stable\" 54% 28.5MB/s 2.87" + "\r\u001b[7mDownload snap \"microk8s\" (5372) from channel\u001b[0m \"1.27/stable\" 56% 28.6MB/s 2.75" + "\r\u001b[7mDownload snap \"microk8s\" (5372) from channel \u001b[0m\"1.27/stable\" 57% 28.7MB/s 2.63" + "\r\u001b[7mDownload snap \"microk8s\" (5372) from channel \"1\u001b[0m.27/stable\" 60% 28.9MB/s 2.47" + "\r\u001b[7mDownload snap \"microk8s\" (5372) from channel \"1.2\u001b[0m7/stable\" 62% 29.0MB/s 2.35" + "\r\u001b[7mDownload snap \"microk8s\" (5372) from channel \"1.27\u001b[0m/stable\" 63% 29.1MB/s 2.23" + "\r\u001b[7mDownload snap \"microk8s\" (5372) from channel \"1.27/s\u001b[0mtable\" 65% 29.2MB/s 2.10" + "\r\u001b[7mDownload snap \"microk8s\" (5372) from channel \"1.27/st\u001b[0mable\" 67% 29.4MB/s 1.97" + "\r\u001b[7mDownload snap \"microk8s\" (5372) from channel \"1.27/stab\u001b[0mle\" 69% 29.5MB/s 1.85" + "\r\u001b[7mDownload snap \"microk8s\" (5372) from channel \"1.27/stabl\u001b[0me\" 71% 29.5MB/s 1.74" + "\r\u001b[7mDownload snap \"microk8s\" (5372) from channel \"1.27/stable\"\u001b[0m 73% 29.7MB/s 1.59" + "\r\u001b[7mDownload snap \"microk8s\" (5372) from channel \"1.27/stable\" \u001b[0m 75% 29.8MB/s 1.48" + "\r\u001b[7mDownload snap \"microk8s\" (5372) from channel \"1.27/stable\" \u001b[0m 77% 29.8MB/s 1.37" + "\r\u001b[7mDownload snap \"microk8s\" (5372) from channel \"1.27/stable\" 7\u001b[0m9% 29.9MB/s 1.26" + "\r\u001b[7mDownload snap \"microk8s\" (5372) from channel \"1.27/stable\" 81\u001b[0m% 30.0MB/s 1.14" + "\r\u001b[7mDownload snap \"microk8s\" (5372) from channel \"1.27/stable\" 83% \u001b[0m30.1MB/s 1.01" + "\r\u001b[7mDownload snap \"microk8s\" (5372) from channel \"1.27/stable\" 84% 3\u001b[0m0.1MB/s 919m" + "\r\u001b[7mDownload snap \"microk8s\" (5372) from channel \"1.27/stable\" 86% 30.\u001b[0m1MB/s 810m" + "\r\u001b[7mDownload snap \"microk8s\" (5372) from channel \"1.27/stable\" 88% 30.2\u001b[0mMB/s 676m" + "\r\u001b[7mDownload snap \"microk8s\" (5372) from channel \"1.27/stable\" 91% 30.3MB\u001b[0m/s 555m" + "\r\u001b[7mDownload snap \"microk8s\" (5372) from channel \"1.27/stable\" 93% 30.4MB/s\u001b[0m 436m" + "\r\u001b[7mDownload snap \"microk8s\" (5372) from channel \"1.27/stable\" 95% 30.5MB/s \u001b[0m317m" + "\r\u001b[7mDownload snap \"microk8s\" (5372) from channel \"1.27/stable\" 96% 30.5MB/s 21\u001b[0m1m" + "\r\u001b[7mDownload snap \"microk8s\" (5372) from channel \"1.27/stable\" 98% 30.5MB/s 117\u001b[0mm" + "\r\u001b[7mDownload snap \"microk8s\" (5372) from channel \"1.27/stable\" 100% 30.5MB/s 11m\u001b[0m" + "\r\u001b[7mDownload snap \"microk8s\" (5372) from channel \"1.27/stable\" 100% 30.0MB/s 0.0ns\u001b[0" + "\rFetch and check assertions for snap \"microk8s\" (5372) " + "\rMount snap \"microk8s\" (5372) \\" + "\rMount snap \"microk8s\" (5372) " + "\rMount snap \"microk8s\" (5372) " + "\rMount snap \"microk8s\" (5372) " + "\rSetup snap \"microk8s\" (5372) security profiles \\" + "\rSetup snap \"microk8s\" (5372) security profiles " + "\rSetup snap \"microk8s\" (5372) security profiles " + "\rSetup snap \"microk8s\" (5372) security profiles " + "\rSetup snap \"microk8s\" (5372) security profiles \\" + "\rSetup snap \"microk8s\" (5372) security profiles " + "\rSetup snap \"microk8s\" (5372) security profiles " + "\rSetup snap \"microk8s\" (5372) security profiles " + "\rSetup snap \"microk8s\" (5372) security profiles \\" + "\rSetup snap \"microk8s\" (5372) security profiles " + "\rSetup snap \"microk8s\" (5372) security profiles " + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present \\" + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present \\" + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present \\" + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present \\" + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present \\" + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present \\" + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present \\" + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present \\" + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present \\" + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present \\" + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present \\" + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present \\" + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present \\" + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present \\" + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present " + "\rRun install hook of \"microk8s\" snap if present " + "\rStart snap \"microk8s\" (5372) services \\" + "\rStart snap \"microk8s\" (5372) services " + "\rStart snap \"microk8s\" (5372) services " + "\rStart snap \"microk8s\" (5372) services " + "\rStart snap \"microk8s\" (5372) services \\" + "\rStart snap \"microk8s\" (5372) services " + "\rStart snap \"microk8s\" (5372) services " + "\rStart snap \"microk8s\" (5372) services " + "\rStart snap \"microk8s\" (5372) services \\" + "\rStart snap \"microk8s\" (5372) services " + "\rStart snap \"microk8s\" (5372) services " + "\rStart snap \"microk8s\" (5372) services " + "\rStart snap \"microk8s\" (5372) services \\" + "\rStart snap \"microk8s\" (5372) services " + "\rStart snap \"microk8s\" (5372) services " + "\rStart snap \"microk8s\" (5372) services " + "\rStart snap \"microk8s\" (5372) services \\" + "\rRun configure hook of \"microk8s\" snap if present " + "\rRun configure hook of \"microk8s\" snap if present " + "\rRun configure hook of \"microk8s\" snap if present " + "\rRun configure hook of \"microk8s\" snap if present \\" + "\rRun configure hook of \"microk8s\" snap if present " + "\rRun configure hook of \"microk8s\" snap if present " + "\rRun configure hook of \"microk8s\" snap if present " + "\rRun configure hook of \"microk8s\" snap if present \\" + "\rRun configure hook of \"microk8s\" snap if present " + "\rRun configure hook of \"microk8s\" snap if present " + "\rRun configure hook of \"microk8s\" snap if present " + "\rRun configure hook of \"microk8s\" snap if present \\" + "\rRun configure hook of \"microk8s\" snap if present " + "\rRun configure hook of \"microk8s\" snap if present " + "\rRun configure hook of \"microk8s\" snap if present " + "\rRun configure hook of \"microk8s\" snap if present \\" + "\rRun configure hook of \"microk8s\" snap if present " + "\rRun configure hook of \"microk8s\" snap if present " + "\rRun configure hook of \"microk8s\" snap if present " + "\rRun configure hook of \"microk8s\" snap if present \\" + "\rRun configure hook of \"microk8s\" snap if present " + "\rRun service command \"restart\" for services [\"daemon-apiserver-proxy\"] of snap \"" + "\r\u001b[0m\u001b[?25h\u001b[Kmicrok8s (1.27/stable) v1.27.2 from Canonical** installed\n" +) + +issue_6803_kubectl_out = ( + "\rEnsure prerequisites for \"kubectl\" are available /" + "\rDownload snap \"kubectl\" (5372) from channel \"1.27/stable\" " + "\rDownload snap \"kubectl\" (5372) from channel \"1.27/stable\" \\" + "\rDownload snap \"kubectl\" (5372) from channel \"1.27/stable\" " + "\rDownload snap \"kubectl\" (5372) from channel \"1.27/stable\" /\u001b[?25" + "\r\u001b[7m\u001b[0mDownload snap \"kubectl\" (5372) from channel \"1.27/stable\" 0% 0B/s ages" + "\r\u001b[7m\u001b[0mDownload snap \"kubectl\" (5372) from channel \"1.27/stable\" 0% 0B/s ages" + "\r\u001b[7m\u001b[0mDownload snap \"kubectl\" (5372) from channel \"1.27/stable\" 0% 0B/s ages" + "\r\u001b[7m\u001b[0mDownload snap \"kubectl\" (5372) from channel \"1.27/stable\" 0% 880kB/s 3m21" + "\r\u001b[7m\u001b[0mDownload snap \"kubectl\" (5372) from channel \"1.27/stable\" 1% 2.82MB/s 1m02" + "\r\u001b[7mD\u001b[0mownload snap \"kubectl\" (5372) from channel \"1.27/stable\" 2% 4.71MB/s 37.0" + "\r\u001b[7mDo\u001b[0mwnload snap \"kubectl\" (5372) from channel \"1.27/stable\" 4% 9.09MB/s 18.8" + "\r\u001b[7mDown\u001b[0mload snap \"kubectl\" (5372) from channel \"1.27/stable\" 6% 12.4MB/s 13.5" + "\r\u001b[7mDownl\u001b[0moad snap \"kubectl\" (5372) from channel \"1.27/stable\" 7% 14.5MB/s 11.3" + "\r\u001b[7mDownloa\u001b[0md snap \"kubectl\" (5372) from channel \"1.27/stable\" 9% 15.9MB/s 10.1" + "\r\u001b[7mDownload \u001b[0msnap \"kubectl\" (5372) from channel \"1.27/stable\" 11% 18.0MB/s 8.75" + "\r\u001b[7mDownload s\u001b[0mnap \"kubectl\" (5372) from channel \"1.27/stable\" 13% 19.4MB/s 7.91" + "\r\u001b[7mDownload sn\u001b[0map \"kubectl\" (5372) from channel \"1.27/stable\" 15% 20.1MB/s 7.50" + "\r\u001b[7mDownload snap\u001b[0m \"kubectl\" (5372) from channel \"1.27/stable\" 17% 20.9MB/s 7.05" + "\r\u001b[7mDownload snap \"\u001b[0mkubectl\" (5372) from channel \"1.27/stable\" 19% 22.1MB/s 6.50" + "\r\u001b[7mDownload snap \"m\u001b[0kubectl\" (5372) from channel \"1.27/stable\" 21% 22.9MB/s 6.11" + "\r\u001b[7mDownload snap \"mic\u001b[0mrok8s\" (5372) from channel \"1.27/stable\" 23% 23.2MB/s 5.90" + "\r\u001b[7mDownload snap \"micr\u001b[0mok8s\" (5372) from channel \"1.27/stable\" 25% 23.9MB/s 5.58" + "\r\u001b[7mDownload snap \"microk\u001b[0m8s\" (5372) from channel \"1.27/stable\" 27% 24.5MB/s 5.30" + "\r\u001b[7mDownload snap \"microk8\u001b[0ms\" (5372) from channel \"1.27/stable\" 29% 24.9MB/s 5.09" + "\r\u001b[7mDownload snap \"kubectl\"\u001b[0m (5372) from channel \"1.27/stable\" 31% 25.4MB/s 4.85" + "\r\u001b[7mDownload snap \"kubectl\" (\u001b[0m5372) from channel \"1.27/stable\" 33% 25.8MB/s 4.63" + "\r\u001b[7mDownload snap \"kubectl\" (5\u001b[0m372) from channel \"1.27/stable\" 35% 26.2MB/s 4.42" + "\r\u001b[7mDownload snap \"kubectl\" (53\u001b[0m72) from channel \"1.27/stable\" 36% 26.3MB/s 4.30" + "\r\u001b[7mDownload snap \"kubectl\" (5372\u001b[0m) from channel \"1.27/stable\" 38% 26.7MB/s 4.10" + "\r\u001b[7mDownload snap \"kubectl\" (5372) \u001b[0mfrom channel \"1.27/stable\" 40% 26.9MB/s 3.95" + "\r\u001b[7mDownload snap \"kubectl\" (5372) f\u001b[0mrom channel \"1.27/stable\" 42% 27.2MB/s 3.77" + "\r\u001b[7mDownload snap \"kubectl\" (5372) fro\u001b[0mm channel \"1.27/stable\" 44% 27.4MB/s 3.63" + "\r\u001b[7mDownload snap \"kubectl\" (5372) from\u001b[0m channel \"1.27/stable\" 46% 27.8MB/s 3.44" + "\r\u001b[7mDownload snap \"kubectl\" (5372) from c\u001b[0mhannel \"1.27/stable\" 48% 27.9MB/s 3.31" + "\r\u001b[7mDownload snap \"kubectl\" (5372) from cha\u001b[0mnnel \"1.27/stable\" 50% 28.1MB/s 3.15" + "\r\u001b[7mDownload snap \"kubectl\" (5372) from chan\u001b[0mnel \"1.27/stable\" 52% 28.3MB/s 3.02" + "\r\u001b[7mDownload snap \"kubectl\" (5372) from channe\u001b[0ml \"1.27/stable\" 54% 28.5MB/s 2.87" + "\r\u001b[7mDownload snap \"kubectl\" (5372) from channel\u001b[0m \"1.27/stable\" 56% 28.6MB/s 2.75" + "\r\u001b[7mDownload snap \"kubectl\" (5372) from channel \u001b[0m\"1.27/stable\" 57% 28.7MB/s 2.63" + "\r\u001b[7mDownload snap \"kubectl\" (5372) from channel \"1\u001b[0m.27/stable\" 60% 28.9MB/s 2.47" + "\r\u001b[7mDownload snap \"kubectl\" (5372) from channel \"1.2\u001b[0m7/stable\" 62% 29.0MB/s 2.35" + "\r\u001b[7mDownload snap \"kubectl\" (5372) from channel \"1.27\u001b[0m/stable\" 63% 29.1MB/s 2.23" + "\r\u001b[7mDownload snap \"kubectl\" (5372) from channel \"1.27/s\u001b[0mtable\" 65% 29.2MB/s 2.10" + "\r\u001b[7mDownload snap \"kubectl\" (5372) from channel \"1.27/st\u001b[0mable\" 67% 29.4MB/s 1.97" + "\r\u001b[7mDownload snap \"kubectl\" (5372) from channel \"1.27/stab\u001b[0mle\" 69% 29.5MB/s 1.85" + "\r\u001b[7mDownload snap \"kubectl\" (5372) from channel \"1.27/stabl\u001b[0me\" 71% 29.5MB/s 1.74" + "\r\u001b[7mDownload snap \"kubectl\" (5372) from channel \"1.27/stable\"\u001b[0m 73% 29.7MB/s 1.59" + "\r\u001b[7mDownload snap \"kubectl\" (5372) from channel \"1.27/stable\" \u001b[0m 75% 29.8MB/s 1.48" + "\r\u001b[7mDownload snap \"kubectl\" (5372) from channel \"1.27/stable\" \u001b[0m 77% 29.8MB/s 1.37" + "\r\u001b[7mDownload snap \"kubectl\" (5372) from channel \"1.27/stable\" 7\u001b[0m9% 29.9MB/s 1.26" + "\r\u001b[7mDownload snap \"kubectl\" (5372) from channel \"1.27/stable\" 81\u001b[0m% 30.0MB/s 1.14" + "\r\u001b[7mDownload snap \"kubectl\" (5372) from channel \"1.27/stable\" 83% \u001b[0m30.1MB/s 1.01" + "\r\u001b[7mDownload snap \"kubectl\" (5372) from channel \"1.27/stable\" 84% 3\u001b[0m0.1MB/s 919m" + "\r\u001b[7mDownload snap \"kubectl\" (5372) from channel \"1.27/stable\" 86% 30.\u001b[0m1MB/s 810m" + "\r\u001b[7mDownload snap \"kubectl\" (5372) from channel \"1.27/stable\" 88% 30.2\u001b[0mMB/s 676m" + "\r\u001b[7mDownload snap \"kubectl\" (5372) from channel \"1.27/stable\" 91% 30.3MB\u001b[0m/s 555m" + "\r\u001b[7mDownload snap \"kubectl\" (5372) from channel \"1.27/stable\" 93% 30.4MB/s\u001b[0m 436m" + "\r\u001b[7mDownload snap \"kubectl\" (5372) from channel \"1.27/stable\" 95% 30.5MB/s \u001b[0m317m" + "\r\u001b[7mDownload snap \"kubectl\" (5372) from channel \"1.27/stable\" 96% 30.5MB/s 21\u001b[0m1m" + "\r\u001b[7mDownload snap \"kubectl\" (5372) from channel \"1.27/stable\" 98% 30.5MB/s 117\u001b[0mm" + "\r\u001b[7mDownload snap \"kubectl\" (5372) from channel \"1.27/stable\" 100% 30.5MB/s 11m\u001b[0m" + "\r\u001b[7mDownload snap \"kubectl\" (5372) from channel \"1.27/stable\" 100% 30.0MB/s 0.0ns\u001b[0" + "\rFetch and check assertions for snap \"kubectl\" (5372) " + "\rMount snap \"kubectl\" (5372) \\" + "\rMount snap \"kubectl\" (5372) " + "\rMount snap \"kubectl\" (5372) " + "\rMount snap \"kubectl\" (5372) " + "\rSetup snap \"kubectl\" (5372) security profiles \\" + "\rSetup snap \"kubectl\" (5372) security profiles " + "\rSetup snap \"kubectl\" (5372) security profiles " + "\rSetup snap \"kubectl\" (5372) security profiles " + "\rSetup snap \"kubectl\" (5372) security profiles \\" + "\rSetup snap \"kubectl\" (5372) security profiles " + "\rSetup snap \"kubectl\" (5372) security profiles " + "\rSetup snap \"kubectl\" (5372) security profiles " + "\rSetup snap \"kubectl\" (5372) security profiles \\" + "\rSetup snap \"kubectl\" (5372) security profiles " + "\rSetup snap \"kubectl\" (5372) security profiles " + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present \\" + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present \\" + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present \\" + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present \\" + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present \\" + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present \\" + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present \\" + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present \\" + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present \\" + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present \\" + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present \\" + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present \\" + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present \\" + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present \\" + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present " + "\rRun install hook of \"kubectl\" snap if present " + "\rStart snap \"kubectl\" (5372) services \\" + "\rStart snap \"kubectl\" (5372) services " + "\rStart snap \"kubectl\" (5372) services " + "\rStart snap \"kubectl\" (5372) services " + "\rStart snap \"kubectl\" (5372) services \\" + "\rStart snap \"kubectl\" (5372) services " + "\rStart snap \"kubectl\" (5372) services " + "\rStart snap \"kubectl\" (5372) services " + "\rStart snap \"kubectl\" (5372) services \\" + "\rStart snap \"kubectl\" (5372) services " + "\rStart snap \"kubectl\" (5372) services " + "\rStart snap \"kubectl\" (5372) services " + "\rStart snap \"kubectl\" (5372) services \\" + "\rStart snap \"kubectl\" (5372) services " + "\rStart snap \"kubectl\" (5372) services " + "\rStart snap \"kubectl\" (5372) services " + "\rStart snap \"kubectl\" (5372) services \\" + "\rRun configure hook of \"kubectl\" snap if present " + "\rRun configure hook of \"kubectl\" snap if present " + "\rRun configure hook of \"kubectl\" snap if present " + "\rRun configure hook of \"kubectl\" snap if present \\" + "\rRun configure hook of \"kubectl\" snap if present " + "\rRun configure hook of \"kubectl\" snap if present " + "\rRun configure hook of \"kubectl\" snap if present " + "\rRun configure hook of \"kubectl\" snap if present \\" + "\rRun configure hook of \"kubectl\" snap if present " + "\rRun configure hook of \"kubectl\" snap if present " + "\rRun configure hook of \"kubectl\" snap if present " + "\rRun configure hook of \"kubectl\" snap if present \\" + "\rRun configure hook of \"kubectl\" snap if present " + "\rRun configure hook of \"kubectl\" snap if present " + "\rRun configure hook of \"kubectl\" snap if present " + "\rRun configure hook of \"kubectl\" snap if present \\" + "\rRun configure hook of \"kubectl\" snap if present " + "\rRun configure hook of \"kubectl\" snap if present " + "\rRun configure hook of \"kubectl\" snap if present " + "\rRun configure hook of \"kubectl\" snap if present \\" + "\rRun configure hook of \"kubectl\" snap if present " + "\rRun service command \"restart\" for services [\"daemon-apiserver-proxy\"] of snap \"" + "\r\u001b[0m\u001b[?25h\u001b[Kkubectl (1.27/stable) v1.27.2 from Canonical** installed\n" +) + +TEST_CASES = [ + ModuleTestCase( + 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'], + environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False}, + rc=0, + out='name: hello-world\n', + err="", + ), + RunCmdCall( + command=['/testbin/snap', 'list'], + environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False}, + rc=0, + out="", + err="", + ), + RunCmdCall( + command=['/testbin/snap', 'install', 'hello-world'], + environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False}, + rc=0, + out="hello-world (12345/stable) v12345 from Canonical** installed\n", + err="", + ), + RunCmdCall( + command=['/testbin/snap', 'list'], + environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False}, + rc=0, + out=( + "Name Version Rev Tracking Publisher Notes" + "core20 20220826 1623 latest/stable canonical** base" + "lxd 5.6-794016a 23680 latest/stable/… canonical** -" + "hello-world 5.6-794016a 23680 latest/stable/… canonical** -" + "snapd 2.57.4 17336 latest/stable canonical** snapd" + ""), + err="", + ), + ] + ), + ModuleTestCase( + 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'], + environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False}, + rc=0, + out='name: microk8s\n---\nname: kubectl\n', + err="", + ), + RunCmdCall( + command=['/testbin/snap', 'list'], + environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False}, + rc=0, + out=issue_6803_status_out, + err="", + ), + RunCmdCall( + command=['/testbin/snap', 'install', '--classic', 'microk8s'], + environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False}, + rc=0, + out=issue_6803_microk8s_out, + err="", + ), + RunCmdCall( + command=['/testbin/snap', 'install', '--classic', 'kubectl'], + environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False}, + rc=0, + out=issue_6803_kubectl_out, + err="", + ), + RunCmdCall( + command=['/testbin/snap', 'list'], + environ={'environ_update': {'LANGUAGE': 'C', 'LC_ALL': 'C'}, 'check_rc': False}, + rc=0, + out=( + "Name Version Rev Tracking Publisher Notes" + "core20 20220826 1623 latest/stable canonical** base" + "lxd 5.6-794016a 23680 latest/stable/… canonical** -" + "microk8s 5.6-794016a 23680 latest/stable/… canonical** -" + "kubectl 5.6-794016a 23680 latest/stable/… canonical** -" + "snapd 2.57.4 17336 latest/stable canonical** snapd" + ""), + err="", + ), + ] + ), +] + +helper = Helper.from_list(snap.main, TEST_CASES) +patch_bin = helper.cmd_fixture +test_module = helper.test_module diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_usb_facts.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_usb_facts.py new file mode 100644 index 000000000..084433492 --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_usb_facts.py @@ -0,0 +1,105 @@ +# 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 ansible_collections.community.general.tests.unit.compat import mock +from ansible_collections.community.general.tests.unit.compat import unittest +from ansible.module_utils import basic +from ansible.module_utils.common.text.converters import to_bytes +from ansible_collections.community.general.plugins.modules import usb_facts + + +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) + + +def get_bin_path(self, arg, required=False): + """Mock AnsibleModule.get_bin_path""" + if arg == 'lsusb': + return '/usr/bin/lsusb' + else: + if required: + fail_json(msg='%r not found !' % arg) + + +class TestUsbFacts(unittest.TestCase): + + def setUp(self): + self.mock_module_helper = mock.patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json, + get_bin_path=get_bin_path) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + self.testing_data = [ + { + "input": "Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub", + "bus": "001", + "device": "001", + "id": "1d6b:0002", + "name": "Linux Foundation 2.0 root hub" + }, + { + "input": "Bus 003 Device 002: ID 8087:8008 Intel Corp. Integrated Rate Matching Hub", + "bus": "003", + "device": "002", + "id": "8087:8008", + "name": "Intel Corp. Integrated Rate Matching Hub" + } + ] + self.output_fields = ["bus", "device", "id", "name"] + + def test_parsing_single_line(self): + for data in self.testing_data: + with mock.patch.object(basic.AnsibleModule, 'run_command') as mock_run_command: + command_output = data["input"] + mock_run_command.return_value = 0, command_output, None + with self.assertRaises(AnsibleExitJson) as result: + set_module_args({}) + usb_facts.main() + for output_field in self.output_fields: + self.assertEqual(result.exception.args[0]["ansible_facts"]["usb_devices"][0][output_field], data[output_field]) + + def test_parsing_multiple_lines(self): + input = "" + for data in self.testing_data: + input += ("%s\n" % data["input"]) + with mock.patch.object(basic.AnsibleModule, 'run_command') as mock_run_command: + mock_run_command.return_value = 0, input, None + with self.assertRaises(AnsibleExitJson) as result: + set_module_args({}) + usb_facts.main() + for index in range(0, len(self.testing_data)): + for output_field in self.output_fields: + self.assertEqual(result.exception.args[0]["ansible_facts"]["usb_devices"][index][output_field], + self.testing_data[index][output_field]) diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_xfconf.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_xfconf.py index c979fd8d2..fbc2dae5f 100644 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_xfconf.py +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_xfconf.py @@ -12,301 +12,9 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -import json from ansible_collections.community.general.plugins.modules import xfconf +from .helper import Helper -import pytest -TESTED_MODULE = xfconf.__name__ - - -@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] - - -@pytest.mark.parametrize('patch_ansible_module, testcase', - TEST_CASES, - ids=TEST_CASES_IDS, - indirect=['patch_ansible_module']) -@pytest.mark.usefixtures('patch_ansible_module') -def test_xfconf(mocker, capfd, patch_xfconf, testcase): - """ - Run unit tests for test cases listen 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]) +Helper.from_module(xfconf, __name__) diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_xfconf.yaml b/ansible_collections/community/general/tests/unit/plugins/modules/test_xfconf.yaml new file mode 100644 index 000000000..908154df2 --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_xfconf.yaml @@ -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: &env-def {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: *env-def + 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: *env-def + rc: 0 + out: "90\n" + err: "" + - command: [/testbin/xfconf-query, --channel, xfwm4, --property, /general/inactive_opacity, --create, --type, int, --set, '90'] + environ: *env-def + 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: *env-def + rc: 0 + out: "true\n" + err: "" + - command: [/testbin/xfconf-query, --channel, xfce4-session, --property, /general/SaveOnExit, --create, --type, bool, --set, 'false'] + environ: *env-def + 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: *env-def + 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: *env-def + 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: *env-def + 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: *env-def + 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: *env-def + 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: *env-def + rc: 0 + out: "" + err: "" diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_xfconf_info.py b/ansible_collections/community/general/tests/unit/plugins/modules/test_xfconf_info.py index dfcd4f33a..67c63dda0 100644 --- a/ansible_collections/community/general/tests/unit/plugins/modules/test_xfconf_info.py +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_xfconf_info.py @@ -5,168 +5,9 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -import json from ansible_collections.community.general.plugins.modules import xfconf_info +from .helper import Helper -import pytest -TESTED_MODULE = xfconf_info.__name__ - - -@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] - - -@pytest.mark.parametrize('patch_ansible_module, testcase', - TEST_CASES, - ids=TEST_CASES_IDS, - indirect=['patch_ansible_module']) -@pytest.mark.usefixtures('patch_ansible_module') -def test_xfconf_info(mocker, capfd, patch_xfconf_info, testcase): - """ - Run unit tests for test cases listen 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 +Helper.from_module(xfconf_info, __name__) diff --git a/ansible_collections/community/general/tests/unit/plugins/modules/test_xfconf_info.yaml b/ansible_collections/community/general/tests/unit/plugins/modules/test_xfconf_info.yaml new file mode 100644 index 000000000..519a87fdb --- /dev/null +++ b/ansible_collections/community/general/tests/unit/plugins/modules/test_xfconf_info.yaml @@ -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: &env-def {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: *env-def + 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: *env-def + 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: *env-def + 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: *env-def + rc: 0 + out: | + /general/wrap_cycle + /general/wrap_layout + /general/wrap_resistance + /general/wrap_windows + /general/wrap_workspaces + /general/zoom_desktop + err: "" diff --git a/ansible_collections/community/general/tests/unit/requirements.txt b/ansible_collections/community/general/tests/unit/requirements.txt index 0aa7c1fc9..218fe4567 100644 --- a/ansible_collections/community/general/tests/unit/requirements.txt +++ b/ansible_collections/community/general/tests/unit/requirements.txt @@ -6,10 +6,12 @@ unittest2 ; python_version < '2.7' importlib ; python_version < '2.7' # requirement for the memcached cache plugin -python-memcached +python-memcached < 1.60 ; python_version < '3.6' +python-memcached ; python_version >= '3.6' # requirement for the redis cache plugin redis +async-timeout ; python_version == '3.11' # requirement for the linode module linode-python # APIv3 @@ -43,4 +45,12 @@ dataclasses ; python_version == '3.6' elastic-apm ; python_version >= '3.6' # requirements for scaleway modules -passlib[argon2]
\ No newline at end of file +passlib[argon2] + +# requirements for the proxmox modules +proxmoxer < 2.0.0 ; python_version >= '2.7' and python_version <= '3.6' +proxmoxer ; python_version > '3.6' + +#requirements for nomad_token modules +python-nomad < 2.0.0 ; python_version <= '3.6' +python-nomad >= 2.0.0 ; python_version >= '3.7' diff --git a/ansible_collections/community/general/tests/utils/constraints.txt b/ansible_collections/community/general/tests/utils/constraints.txt index 4fb5276e2..c4d0312d8 100644 --- a/ansible_collections/community/general/tests/utils/constraints.txt +++ b/ansible_collections/community/general/tests/utils/constraints.txt @@ -19,7 +19,7 @@ wheel < 0.30.0 ; python_version < '2.7' # wheel 0.30.0 and later require python yamllint != 1.8.0, < 1.14.0 ; python_version < '2.7' # yamllint 1.8.0 and 1.14.0+ require python 2.7+ pycrypto >= 2.6 # Need features found in 2.6 and greater ncclient >= 0.5.2 # Need features added in 0.5.2 and greater -idna < 2.6, >= 2.5 # linode requires idna < 2.9, >= 2.5, requests requires idna < 2.6, but cryptography will cause the latest version to be installed instead +# idna < 2.6, >= 2.5 # linode requires idna < 2.9, >= 2.5, requests requires idna < 2.6, but cryptography will cause the latest version to be installed instead paramiko < 2.4.0 ; python_version < '2.7' # paramiko 2.4.0 drops support for python 2.6 python-nomad < 2.0.0 ; python_version <= '3.7' # python-nomad 2.0.0 needs Python 3.7+ pytest < 3.3.0 ; python_version < '2.7' # pytest 3.3.0 drops support for python 2.6 diff --git a/ansible_collections/community/general/tests/utils/shippable/shippable.sh b/ansible_collections/community/general/tests/utils/shippable/shippable.sh index e98680438..e288cca1c 100755 --- a/ansible_collections/community/general/tests/utils/shippable/shippable.sh +++ b/ansible_collections/community/general/tests/utils/shippable/shippable.sh @@ -65,16 +65,7 @@ else retry pip install "https://github.com/ansible/ansible/archive/stable-${ansible_version}.tar.gz" --disable-pip-version-check fi -if [ "${SHIPPABLE_BUILD_ID:-}" ]; then - export ANSIBLE_COLLECTIONS_PATHS="${HOME}/.ansible" - SHIPPABLE_RESULT_DIR="$(pwd)/shippable" - TEST_DIR="${ANSIBLE_COLLECTIONS_PATHS}/ansible_collections/community/general" - mkdir -p "${TEST_DIR}" - cp -aT "${SHIPPABLE_BUILD_DIR}" "${TEST_DIR}" - cd "${TEST_DIR}" -else - export ANSIBLE_COLLECTIONS_PATHS="${PWD}/../../../" -fi +export ANSIBLE_COLLECTIONS_PATHS="${PWD}/../../../" if [ "${test}" == "sanity/extra" ]; then retry pip install junit-xml --disable-pip-version-check |