From 016cd0691cea5a5e085d13c5f82b4f69b9214078 Mon Sep 17 00:00:00 2001 From: Pilou Date: Wed, 16 Aug 2017 16:35:56 +0200 Subject: [PATCH] alternatives: handle absent link, add integration tests (#27967) * alternatives: add integration tests * alternatives: handle absent link (fix AttributeError) Error occurred at least on Debian Stretch and OpenSuse 42.2: Traceback (most recent call last): File "/tmp/ansible_RY6X41/ansible_module_alternatives.py", line 161, in main() File "/tmp/ansible_RY6X41/ansible_module_alternatives.py", line 113, in main current_path = current_path_regex.search(display_output).group(1) AttributeError: 'NoneType' object has no attribute 'group' update-alternatives stdout sample: dummy - manual mode link best version is /usr/bin/dummy1 link currently absent link dummy is /usr/bin/dummy * alternatives: PEP 8 fixes * alternatives: fix copyright in integration tests * alternatives: nested loops handle more than 2 items Thanks to Michael Scherer (@mscherer) for pointing that. * alternatives: enable integration tests --- lib/ansible/modules/system/alternatives.py | 18 +++--- test/integration/targets/alternatives/aliases | 5 ++ .../targets/alternatives/tasks/main.yml | 62 +++++++++++++++++++ .../alternatives/tasks/remove_links.yml | 7 +++ .../targets/alternatives/tasks/setup.yml | 14 +++++ .../targets/alternatives/tasks/setup_test.yml | 22 +++++++ .../targets/alternatives/tasks/test.yml | 53 ++++++++++++++++ .../targets/alternatives/tasks/tests.yml | 15 +++++ .../alternatives/tasks/tests_set_priority.yml | 23 +++++++ .../alternatives/templates/dummy_alternative | 12 ++++ .../alternatives/templates/dummy_command | 2 + .../targets/alternatives/vars/Debian.yml | 2 + .../targets/alternatives/vars/Suse.yml | 2 + .../targets/alternatives/vars/default.yml | 2 + test/sanity/pep8/legacy-files.txt | 1 - 15 files changed, 231 insertions(+), 9 deletions(-) create mode 100644 test/integration/targets/alternatives/aliases create mode 100644 test/integration/targets/alternatives/tasks/main.yml create mode 100644 test/integration/targets/alternatives/tasks/remove_links.yml create mode 100644 test/integration/targets/alternatives/tasks/setup.yml create mode 100644 test/integration/targets/alternatives/tasks/setup_test.yml create mode 100644 test/integration/targets/alternatives/tasks/test.yml create mode 100644 test/integration/targets/alternatives/tasks/tests.yml create mode 100644 test/integration/targets/alternatives/tasks/tests_set_priority.yml create mode 100644 test/integration/targets/alternatives/templates/dummy_alternative create mode 100644 test/integration/targets/alternatives/templates/dummy_command create mode 100644 test/integration/targets/alternatives/vars/Debian.yml create mode 100644 test/integration/targets/alternatives/vars/Suse.yml create mode 100644 test/integration/targets/alternatives/vars/default.yml diff --git a/lib/ansible/modules/system/alternatives.py b/lib/ansible/modules/system/alternatives.py index ab7deb7cba8..36c3ce8b315 100644 --- a/lib/ansible/modules/system/alternatives.py +++ b/lib/ansible/modules/system/alternatives.py @@ -77,12 +77,12 @@ from ansible.module_utils.basic import AnsibleModule def main(): module = AnsibleModule( - argument_spec = dict( - name = dict(required=True), - path = dict(required=True, type='path'), - link = dict(required=False, type='path'), - priority = dict(required=False, type='int', - default=50), + argument_spec=dict( + name=dict(required=True), + path=dict(required=True, type='path'), + link=dict(required=False, type='path'), + priority=dict(required=False, type='int', + default=50), ), supports_check_mode=True, ) @@ -93,7 +93,7 @@ def main(): link = params['link'] priority = params['priority'] - UPDATE_ALTERNATIVES = module.get_bin_path('update-alternatives',True) + UPDATE_ALTERNATIVES = module.get_bin_path('update-alternatives', True) current_path = None all_alternatives = [] @@ -111,7 +111,9 @@ def main(): re.MULTILINE) alternative_regex = re.compile(r'^(\/.*)\s-\spriority', re.MULTILINE) - current_path = current_path_regex.search(display_output).group(1) + match = current_path_regex.search(display_output) + if match: + current_path = match.group(1) all_alternatives = alternative_regex.findall(display_output) if not link: diff --git a/test/integration/targets/alternatives/aliases b/test/integration/targets/alternatives/aliases new file mode 100644 index 00000000000..66ed33bd3a7 --- /dev/null +++ b/test/integration/targets/alternatives/aliases @@ -0,0 +1,5 @@ +posix/ci/group3 +destructive +needs/root +skip/freebsd +skip/osx diff --git a/test/integration/targets/alternatives/tasks/main.yml b/test/integration/targets/alternatives/tasks/main.yml new file mode 100644 index 00000000000..67ec0ea9b71 --- /dev/null +++ b/test/integration/targets/alternatives/tasks/main.yml @@ -0,0 +1,62 @@ +# Copyright (c) 2017 Pierre-Louis Bonicoli +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: 'setup: create a dummy alternative' + block: + - import_tasks: setup.yml + + ############## + # Test parameters: + # link parameter present / absent ('with_link' variable) + # with / without alternatives defined in alternatives file ('with_alternatives' variable) + # auto / manual ('mode' variable) + + - include_tasks: tests.yml + with_nested: + - [ True, False ] # with_link + - [ True, False ] # with_alternatives + - [ 'auto', 'manual' ] # mode + loop_control: + loop_var: test_conf + + ########## + # Priority + - block: + - include_tasks: remove_links.yml + - include_tasks: setup_test.yml + # at least two iterations again + - include_tasks: tests_set_priority.yml + with_sequence: start=3 end=4 + vars: + with_alternatives: True + mode: auto + + - block: + - include_tasks: remove_links.yml + - include_tasks: setup_test.yml + # at least two iterations again + - include_tasks: tests_set_priority.yml + with_sequence: start=3 end=4 + vars: + with_alternatives: False + mode: auto + always: + - include_tasks: remove_links.yml + + - file: + path: '{{ item }}' + state: absent + with_items: + - '{{ alternatives_dir }}/dummy' + + - file: + path: '/usr/bin/dummy{{ item }}' + state: absent + with_sequence: start=1 end=4 + # *Disable tests on Fedora 24* + # Shippable Fedora 24 image provides chkconfig-1.7-2.fc24.x86_64 but not the + # latest available version (chkconfig-1.8-1.fc24.x86_64). update-alternatives + # in chkconfig-1.7-2 fails when /etc/alternatives/dummy link is missing, + # error is: 'failed to read link /usr/bin/dummy: No such file or directory'. + # Moreover Fedora 24 is no longer maintained. + when: ansible_distribution != 'Fedora' or ansible_distribution_major_version|int > 24 diff --git a/test/integration/targets/alternatives/tasks/remove_links.yml b/test/integration/targets/alternatives/tasks/remove_links.yml new file mode 100644 index 00000000000..690b06069a0 --- /dev/null +++ b/test/integration/targets/alternatives/tasks/remove_links.yml @@ -0,0 +1,7 @@ +- name: remove links + file: + path: '{{ item }}' + state: absent + with_items: + - /etc/alternatives/dummy + - /usr/bin/dummy diff --git a/test/integration/targets/alternatives/tasks/setup.yml b/test/integration/targets/alternatives/tasks/setup.yml new file mode 100644 index 00000000000..39889ff0d23 --- /dev/null +++ b/test/integration/targets/alternatives/tasks/setup.yml @@ -0,0 +1,14 @@ +- include_vars: '{{ item }}' + with_first_found: + - files: + - '{{ ansible_os_family }}.yml' + - 'default.yml' + paths: '../vars' + +- template: + src: dummy_command + dest: '/usr/bin/dummy{{ item }}' + owner: root + group: root + mode: 0755 + with_sequence: start=1 end=4 diff --git a/test/integration/targets/alternatives/tasks/setup_test.yml b/test/integration/targets/alternatives/tasks/setup_test.yml new file mode 100644 index 00000000000..594a69e4db9 --- /dev/null +++ b/test/integration/targets/alternatives/tasks/setup_test.yml @@ -0,0 +1,22 @@ +- template: + src: dummy_alternative + dest: '{{ alternatives_dir }}/dummy' + owner: root + group: root + mode: 0644 + when: with_alternatives or ansible_os_family != 'RedHat' + +# update-alternatives included in Fedora 26 (1.10) & Red Hat 7.4 (1.8) segfaults +# when /dummy file contains only mode and link. Hence the file is +# deleted instead of containing only mode and link. The file below works fine with +# newer version of update-alternatives: +# """ +# auto +# /usr/bin/dummy +# +# +# """ +- file: + path: '{{ alternatives_dir }}/dummy' + state: absent + when: not with_alternatives and ansible_os_family == 'RedHat' diff --git a/test/integration/targets/alternatives/tasks/test.yml b/test/integration/targets/alternatives/tasks/test.yml new file mode 100644 index 00000000000..d44d1f00157 --- /dev/null +++ b/test/integration/targets/alternatives/tasks/test.yml @@ -0,0 +1,53 @@ +- debug: + msg: ' with_alternatives: {{ with_alternatives }}, mode: {{ mode }}' + +- block: + - name: set alternative (using link parameter) + alternatives: + name: dummy + path: '/usr/bin/dummy{{ item }}' + link: '/usr/bin/dummy' + register: alternative + + - name: check expected command was executed + assert: + that: + - 'alternative|success' + - 'alternative|changed' + when: with_link + +- block: + - name: set alternative (without link parameter) + alternatives: + name: dummy + path: '/usr/bin/dummy{{ item }}' + register: alternative + + - name: check expected command was executed + assert: + that: + - 'alternative|success' + - 'alternative|changed' + when: not with_link + +- name: execute dummy command + shell: dummy + register: cmd + +- name: check expected command was executed + assert: + that: + - 'cmd.stdout == "dummy" ~ item' + +- name: 'check mode (manual: alternatives file existed, it has been updated)' + shell: 'head -n1 {{ alternatives_dir }}/dummy | grep "^manual$"' + when: ansible_os_family != 'RedHat' or with_alternatives or item != 1 + +- name: 'check mode (auto: alternatives file didn''t exist, it has been created)' + shell: 'head -n1 {{ alternatives_dir }}/dummy | grep "^auto$"' + when: ansible_os_family == 'RedHat' and not with_alternatives and item == 1 + +- name: check that alternative has been updated + command: "grep -Pzq '/bin/dummy{{ item }}\\n' '{{ alternatives_dir }}/dummy'" + # priority doesn't seem updated + #command: "grep -Pzq '/bin/dummy{{ item }}\\n50' '{{ alternatives_dir }}/dummy'" diff --git a/test/integration/targets/alternatives/tasks/tests.yml b/test/integration/targets/alternatives/tasks/tests.yml new file mode 100644 index 00000000000..e0400dfd81b --- /dev/null +++ b/test/integration/targets/alternatives/tasks/tests.yml @@ -0,0 +1,15 @@ +- block: + - include_tasks: remove_links.yml + - include_tasks: setup_test.yml + # at least two iterations: + # - first will use 'link currently absent', + # - second will receive 'link currently points to' + - include_tasks: test.yml + with_sequence: start=1 end=2 + vars: + with_link: '{{ test_conf[0] }}' + with_alternatives: '{{ test_conf[1] }}' + mode: '{{ test_conf[2] }}' + # update-alternatives included in Fedora 26 (1.10) & Red Hat 7.4 (1.8) doesn't provide + # '--query' switch, 'link' is mandatory for these distributions. + when: ansible_os_family != 'RedHat' or test_conf[0] diff --git a/test/integration/targets/alternatives/tasks/tests_set_priority.yml b/test/integration/targets/alternatives/tasks/tests_set_priority.yml new file mode 100644 index 00000000000..2567753dd8c --- /dev/null +++ b/test/integration/targets/alternatives/tasks/tests_set_priority.yml @@ -0,0 +1,23 @@ +- name: update dummy alternative + alternatives: + name: dummy + path: '/usr/bin/dummy{{ item }}' + link: /usr/bin/dummy + priority: '{{ 60 + item|int }}' + register: alternative + +- name: execute dummy command + shell: dummy + register: cmd + +- name: check if link group is in manual mode + shell: 'head -n1 {{ alternatives_dir }}/dummy | grep "^manual$"' + +- name: check expected command was executed + assert: + that: + - 'alternative|changed' + - '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/test/integration/targets/alternatives/templates/dummy_alternative b/test/integration/targets/alternatives/templates/dummy_alternative new file mode 100644 index 00000000000..5dce8adde79 --- /dev/null +++ b/test/integration/targets/alternatives/templates/dummy_alternative @@ -0,0 +1,12 @@ +{{ mode }} +/usr/bin/dummy + +{% if with_alternatives %} +/usr/bin/dummy1 +40 +/usr/bin/dummy2 +30 + +{% else %} + +{% endif %} diff --git a/test/integration/targets/alternatives/templates/dummy_command b/test/integration/targets/alternatives/templates/dummy_command new file mode 100644 index 00000000000..332d9fe1a9e --- /dev/null +++ b/test/integration/targets/alternatives/templates/dummy_command @@ -0,0 +1,2 @@ +#!/bin/sh +echo dummy{{ item }} diff --git a/test/integration/targets/alternatives/vars/Debian.yml b/test/integration/targets/alternatives/vars/Debian.yml new file mode 100644 index 00000000000..1e83283e4d7 --- /dev/null +++ b/test/integration/targets/alternatives/vars/Debian.yml @@ -0,0 +1,2 @@ +--- +alternatives_dir: /var/lib/dpkg/alternatives/ diff --git a/test/integration/targets/alternatives/vars/Suse.yml b/test/integration/targets/alternatives/vars/Suse.yml new file mode 100644 index 00000000000..37664ddb56e --- /dev/null +++ b/test/integration/targets/alternatives/vars/Suse.yml @@ -0,0 +1,2 @@ +--- +alternatives_dir: /var/lib/rpm/alternatives/ diff --git a/test/integration/targets/alternatives/vars/default.yml b/test/integration/targets/alternatives/vars/default.yml new file mode 100644 index 00000000000..d00123ded3f --- /dev/null +++ b/test/integration/targets/alternatives/vars/default.yml @@ -0,0 +1,2 @@ +--- +alternatives_dir: /var/lib/alternatives/ diff --git a/test/sanity/pep8/legacy-files.txt b/test/sanity/pep8/legacy-files.txt index b6db79130e2..9ae46639e0a 100644 --- a/test/sanity/pep8/legacy-files.txt +++ b/test/sanity/pep8/legacy-files.txt @@ -431,7 +431,6 @@ lib/ansible/modules/storage/netapp/sf_volume_access_group_manager.py lib/ansible/modules/storage/netapp/sf_volume_manager.py lib/ansible/modules/storage/zfs/zfs.py lib/ansible/modules/system/aix_inittab.py -lib/ansible/modules/system/alternatives.py lib/ansible/modules/system/at.py lib/ansible/modules/system/capabilities.py lib/ansible/modules/system/cron.py