From 8635cd84d423ee4c901dbdda3a796f8e296bee10 Mon Sep 17 00:00:00 2001 From: quidame Date: Wed, 10 Jun 2020 18:01:16 +0000 Subject: [PATCH] New module: dpkg_divert (#417) * feature: module dpkg_divert + tests * try to skip non-deb linux distrib * use collection namespace in EXAMPLES * skip unsupported OS/distrib in tasks instead * tests: remove unskipped distribs * apply changes suggested by Andersson007 * Remove ANSIBLE_METADATA (no more needed). * Normalize docstrings (capitalize descriptions, fix styling, use yes/no booleans). * fix descriptions * update DOCUMENTATION * Drop field 'version_added' (no more needed). * Add a note about check_mode support. * use list comprehension * support diff mode * Move 'before'/'after' dicts into 'diff' dictionary. * Set 'diversion' as the actual state (or the state that would be reached, when in check mode). * Always return 'diversion' on handled exits (exit_json & fail_json). * enable 'diff' mode in tests, add missing 'become' --- plugins/modules/dpkg_divert.py | 1 + plugins/modules/system/dpkg_divert.py | 369 +++++++++++++++++ tests/integration/targets/dpkg_divert/aliases | 5 + .../targets/dpkg_divert/tasks/main.yml | 4 + .../targets/dpkg_divert/tasks/prepare.yml | 39 ++ .../dpkg_divert/tasks/tests/01-basic.yml | 287 +++++++++++++ .../dpkg_divert/tasks/tests/02-rename.yml | 380 ++++++++++++++++++ 7 files changed, 1085 insertions(+) create mode 120000 plugins/modules/dpkg_divert.py create mode 100644 plugins/modules/system/dpkg_divert.py create mode 100644 tests/integration/targets/dpkg_divert/aliases create mode 100644 tests/integration/targets/dpkg_divert/tasks/main.yml create mode 100644 tests/integration/targets/dpkg_divert/tasks/prepare.yml create mode 100644 tests/integration/targets/dpkg_divert/tasks/tests/01-basic.yml create mode 100644 tests/integration/targets/dpkg_divert/tasks/tests/02-rename.yml diff --git a/plugins/modules/dpkg_divert.py b/plugins/modules/dpkg_divert.py new file mode 120000 index 0000000000..8d13f3d4b8 --- /dev/null +++ b/plugins/modules/dpkg_divert.py @@ -0,0 +1 @@ +./system/dpkg_divert.py \ No newline at end of file diff --git a/plugins/modules/system/dpkg_divert.py b/plugins/modules/system/dpkg_divert.py new file mode 100644 index 0000000000..e9812e3692 --- /dev/null +++ b/plugins/modules/system/dpkg_divert.py @@ -0,0 +1,369 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017-2020, Yann Amar +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r''' +--- +module: dpkg_divert +short_description: Override a debian package's version of a file +author: + - quidame (@quidame) +description: + - A diversion is for C(dpkg) the knowledge that only a given package + (or the local administrator) is allowed to install a file at a given + location. Other packages shipping their own version of this file will + be forced to I(divert) it, i.e. to install it at another location. It + allows one to keep changes in a file provided by a debian package by + preventing its overwrite at package upgrade. + - This module manages diversions of debian packages files using the + C(dpkg-divert) commandline tool. It can either create or remove a + diversion for a given file, but also update an existing diversion + to modify its I(holder) and/or its I(divert) location. +options: + path: + description: + - The original and absolute path of the file to be diverted or + undiverted. This path is unique, i.e. it is not possible to get + two diversions for the same I(path). + required: true + type: path + state: + description: + - When I(state=absent), remove the diversion of the specified + I(path); when I(state=present), create the diversion if it does + not exist, or update its package I(holder) or I(divert) location, + if it already exists. + type: str + default: present + choices: [absent, present] + holder: + description: + - The name of the package whose copy of file is not diverted, also + known as the diversion holder or the package the diversion belongs + to. + - The actual package does not have to be installed or even to exist + for its name to be valid. If not specified, the diversion is hold + by 'LOCAL', that is reserved by/for dpkg for local diversions. + - This parameter is ignored when I(state=absent). + type: str + divert: + description: + - The location where the versions of file will be diverted. + - Default is to add suffix C(.distrib) to the file path. + - This parameter is ignored when I(state=absent). + type: path + rename: + description: + - Actually move the file aside (when I(state=present)) or back (when + I(state=absent)), but only when changing the state of the diversion. + This parameter has no effect when attempting to add a diversion that + already exists or when removing an unexisting one. + - Unless I(force=true), renaming fails if the destination file already + exists (this lock being a dpkg-divert feature, and bypassing it being + a module feature). + type: bool + default: no + force: + description: + - When I(rename=true) and I(force=true), renaming is performed even if + the target of the renaming exists, i.e. the existing contents of the + file at this location will be lost. + - This parameter is ignored when I(rename=false). + type: bool + default: no +notes: + - This module supports I(check_mode) and I(diff). +requirements: + - dpkg-divert >= 1.15.0 (Debian family) +''' + +EXAMPLES = r''' +- name: Divert /usr/bin/busybox to /usr/bin/busybox.distrib and keep file in place + community.general.dpkg_divert: + path: /usr/bin/busybox + +- name: Divert /usr/bin/busybox by package 'branding' + community.general.dpkg_divert: + path: /usr/bin/busybox + holder: branding + +- name: Divert and rename busybox to busybox.dpkg-divert + community.general.dpkg_divert: + path: /usr/bin/busybox + divert: /usr/bin/busybox.dpkg-divert + rename: yes + +- name: Remove the busybox diversion and move the diverted file back + community.general.dpkg_divert: + path: /usr/bin/busybox + state: absent + rename: yes + force: yes +''' + +RETURN = r''' +commands: + description: The dpkg-divert commands ran internally by the module. + type: list + returned: on_success + elements: str + sample: |- + [ + "/usr/bin/dpkg-divert --no-rename --remove /etc/foobarrc", + "/usr/bin/dpkg-divert --package ansible --no-rename --add /etc/foobarrc" + ] +messages: + description: The dpkg-divert relevant messages (stdout or stderr). + type: list + returned: on_success + elements: str + sample: |- + [ + "Removing 'local diversion of /etc/foobarrc to /etc/foobarrc.distrib'", + "Adding 'diversion of /etc/foobarrc to /etc/foobarrc.distrib by ansible'" + ] +diversion: + description: The status of the diversion after task execution. + type: dict + returned: always + contains: + divert: + description: The location of the diverted file. + type: str + holder: + description: The package holding the diversion. + type: str + path: + description: The path of the file to divert/undivert. + type: str + state: + description: The state of the diversion. + type: str + sample: |- + { + "divert": "/etc/foobarrc.distrib", + "holder": "LOCAL", + "path": "/etc/foobarrc" + "state": "present" + } +''' + + +import re +import os +from distutils.version import LooseVersion + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_bytes, to_native + + +def diversion_state(module, command, path): + diversion = dict(path=path, state='absent', divert=None, holder=None) + rc, out, err = module.run_command([command, '--listpackage', path], check_rc=True) + if out: + diversion['state'] = 'present' + diversion['holder'] = out.rstrip() + rc, out, err = module.run_command([command, '--truename', path], check_rc=True) + diversion['divert'] = out.rstrip() + return diversion + + +def main(): + module = AnsibleModule( + argument_spec=dict( + path=dict(required=True, type='path'), + state=dict(required=False, type='str', default='present', choices=['absent', 'present']), + holder=dict(required=False, type='str'), + divert=dict(required=False, type='path'), + rename=dict(required=False, type='bool', default=False), + force=dict(required=False, type='bool', default=False), + ), + supports_check_mode=True, + ) + + path = module.params['path'] + state = module.params['state'] + holder = module.params['holder'] + divert = module.params['divert'] + rename = module.params['rename'] + force = module.params['force'] + + diversion_wanted = dict(path=path, state=state) + changed = False + + DPKG_DIVERT = module.get_bin_path('dpkg-divert', required=True) + MAINCOMMAND = [DPKG_DIVERT] + + # Option --listpackage is needed and comes with 1.15.0 + rc, stdout, stderr = module.run_command([DPKG_DIVERT, '--version'], check_rc=True) + [current_version] = [x for x in stdout.splitlines()[0].split() if re.match('^[0-9]+[.][0-9]', x)] + if LooseVersion(current_version) < LooseVersion("1.15.0"): + module.fail_json(msg="Unsupported dpkg version (<1.15.0).") + no_rename_is_supported = (LooseVersion(current_version) >= LooseVersion("1.19.1")) + + b_path = to_bytes(path, errors='surrogate_or_strict') + path_exists = os.path.exists(b_path) + # Used for things not doable with a single dpkg-divert command (as forced + # renaming of files, and diversion's 'holder' or 'divert' updates). + target_exists = False + truename_exists = False + + diversion_before = diversion_state(module, DPKG_DIVERT, path) + if diversion_before['state'] == 'present': + b_divert = to_bytes(diversion_before['divert'], errors='surrogate_or_strict') + truename_exists = os.path.exists(b_divert) + + # Append options as requested in the task parameters, but ignore some of + # them when removing the diversion. + if rename: + MAINCOMMAND.append('--rename') + elif no_rename_is_supported: + MAINCOMMAND.append('--no-rename') + + if state == 'present': + if holder and holder != 'LOCAL': + MAINCOMMAND.extend(['--package', holder]) + diversion_wanted['holder'] = holder + else: + MAINCOMMAND.append('--local') + diversion_wanted['holder'] = 'LOCAL' + + if divert: + MAINCOMMAND.extend(['--divert', divert]) + target = divert + else: + target = '%s.distrib' % path + + MAINCOMMAND.extend(['--add', path]) + diversion_wanted['divert'] = target + b_target = to_bytes(target, errors='surrogate_or_strict') + target_exists = os.path.exists(b_target) + + else: + MAINCOMMAND.extend(['--remove', path]) + diversion_wanted['divert'] = None + diversion_wanted['holder'] = None + + # Start to populate the returned objects. + diversion = diversion_before.copy() + maincommand = ' '.join(MAINCOMMAND) + commands = [maincommand] + + if module.check_mode or diversion_wanted == diversion_before: + MAINCOMMAND.insert(1, '--test') + diversion_after = diversion_wanted + + # Just try and see + rc, stdout, stderr = module.run_command(MAINCOMMAND) + + if rc == 0: + messages = [stdout.rstrip()] + + # else... cases of failure with dpkg-divert are: + # - The diversion does not belong to the same package (or LOCAL) + # - The divert filename is not the same (e.g. path.distrib != path.divert) + # - The renaming is forbidden by dpkg-divert (i.e. both the file and the + # diverted file exist) + + elif state != diversion_before['state']: + # There should be no case with 'divert' and 'holder' when creating the + # diversion from none, and they're ignored when removing the diversion. + # So this is all about renaming... + if rename and path_exists and ( + (state == 'absent' and truename_exists) or + (state == 'present' and target_exists)): + if not force: + msg = "Set 'force' param to True to force renaming of files." + module.fail_json(changed=changed, cmd=maincommand, rc=rc, msg=msg, + stderr=stderr, stdout=stdout, diversion=diversion) + else: + msg = "Unexpected error while changing state of the diversion." + module.fail_json(changed=changed, cmd=maincommand, rc=rc, msg=msg, + stderr=stderr, stdout=stdout, diversion=diversion) + + to_remove = path + if state == 'present': + to_remove = target + + if not module.check_mode: + try: + b_remove = to_bytes(to_remove, errors='surrogate_or_strict') + os.unlink(b_remove) + except OSError as e: + msg = 'Failed to remove %s: %s' % (to_remove, to_native(e)) + module.fail_json(changed=changed, cmd=maincommand, rc=rc, msg=msg, + stderr=stderr, stdout=stdout, diversion=diversion) + rc, stdout, stderr = module.run_command(MAINCOMMAND, check_rc=True) + + messages = [stdout.rstrip()] + + # The situation is that we want to modify the settings (holder or divert) + # of an existing diversion. dpkg-divert does not handle this, and we have + # to remove the existing diversion first, and then set a new one. + else: + RMDIVERSION = [DPKG_DIVERT, '--remove', path] + if no_rename_is_supported: + RMDIVERSION.insert(1, '--no-rename') + rmdiversion = ' '.join(RMDIVERSION) + + if module.check_mode: + RMDIVERSION.insert(1, '--test') + + if rename: + MAINCOMMAND.remove('--rename') + if no_rename_is_supported: + MAINCOMMAND.insert(1, '--no-rename') + maincommand = ' '.join(MAINCOMMAND) + + commands = [rmdiversion, maincommand] + rc, rmdout, rmderr = module.run_command(RMDIVERSION, check_rc=True) + + if module.check_mode: + messages = [rmdout.rstrip(), 'Running in check mode'] + else: + rc, stdout, stderr = module.run_command(MAINCOMMAND, check_rc=True) + messages = [rmdout.rstrip(), stdout.rstrip()] + + # Avoid if possible to orphan files (i.e. to dereference them in diversion + # database but let them in place), but do not make renaming issues fatal. + # BTW, this module is not about state of files involved in the diversion. + old = diversion_before['divert'] + new = diversion_wanted['divert'] + if new != old: + b_old = to_bytes(old, errors='surrogate_or_strict') + b_new = to_bytes(new, errors='surrogate_or_strict') + if os.path.exists(b_old) and not os.path.exists(b_new): + try: + os.rename(b_old, b_new) + except OSError as e: + pass + + if not module.check_mode: + diversion_after = diversion_state(module, DPKG_DIVERT, path) + + diversion = diversion_after.copy() + diff = dict() + if module._diff: + diff['before'] = diversion_before + diff['after'] = diversion_after + + if diversion_after != diversion_before: + changed = True + + if diversion_after == diversion_wanted: + module.exit_json(changed=changed, diversion=diversion, + commands=commands, messages=messages, diff=diff) + else: + msg = "Unexpected error: see stdout and stderr for details." + module.fail_json(changed=changed, cmd=maincommand, rc=rc, msg=msg, + stderr=stderr, stdout=stdout, diversion=diversion) + + +if __name__ == '__main__': + main() diff --git a/tests/integration/targets/dpkg_divert/aliases b/tests/integration/targets/dpkg_divert/aliases new file mode 100644 index 0000000000..25786eeda2 --- /dev/null +++ b/tests/integration/targets/dpkg_divert/aliases @@ -0,0 +1,5 @@ +shippable/posix/group4 +skip/aix +skip/osx +skip/rhel +skip/freebsd diff --git a/tests/integration/targets/dpkg_divert/tasks/main.yml b/tests/integration/targets/dpkg_divert/tasks/main.yml new file mode 100644 index 0000000000..336cc5ae76 --- /dev/null +++ b/tests/integration/targets/dpkg_divert/tasks/main.yml @@ -0,0 +1,4 @@ +--- +- name: "include tasks for Debian family" + include_tasks: prepare.yml + when: ansible_pkg_mgr == "apt" diff --git a/tests/integration/targets/dpkg_divert/tasks/prepare.yml b/tests/integration/targets/dpkg_divert/tasks/prepare.yml new file mode 100644 index 0000000000..f30d14a175 --- /dev/null +++ b/tests/integration/targets/dpkg_divert/tasks/prepare.yml @@ -0,0 +1,39 @@ +--- +- name: "set variables for the entire playbook" + set_fact: + foobarrc: "{{ foobarrc }}" + foobarrc_ansible: "{{ foobarrc }}.ansible" + foobarrc_distrib: "{{ foobarrc }}.distrib" + foobarrc_oldtext: "# foobar configuration file\n# Please refer to the documentation for details\n" + foobarrc_oldsha1: "e1c54c36d2fd1b8d67d1826e49b95ac8c0f24c0a" + foobarrc_newtext: "# Custom foobar configuration file\nFOO=bar\nBAR=foo" + foobarrc_newsha1: "3fe6c890519fb48e27c1b0e3e37afb11357d5cac" + vars: + foobarrc: "/etc/foobarrc" + +- name: "remove foobarrc diversion" + dpkg_divert: + path: "{{ foobarrc }}" + state: absent + become: yes + +- name: "remove test files" + file: + path: "{{ dpkg_divert_item }}" + state: absent + loop: + - "{{ foobarrc_ansible }}" + - "{{ foobarrc_distrib }}" + loop_control: + loop_var: dpkg_divert_item + become: yes + + +- block: + - name: "include tasks to perform basic tests (create, remove, update)" + include_tasks: tests/01-basic.yml + + - name: "include tasks to perform other tests (rename)" + include_tasks: tests/02-rename.yml + become: yes + diff: yes diff --git a/tests/integration/targets/dpkg_divert/tasks/tests/01-basic.yml b/tests/integration/targets/dpkg_divert/tasks/tests/01-basic.yml new file mode 100644 index 0000000000..c23db91d56 --- /dev/null +++ b/tests/integration/targets/dpkg_divert/tasks/tests/01-basic.yml @@ -0,0 +1,287 @@ +--- +################################################################################ +# TEST 01: state=present + +- name: "create foobarrc for tests" + copy: + dest: "{{ foobarrc }}" + content: "{{ foobarrc_oldtext }}" + + +- name: "divert foobarrc (check mode, must report a change)" + dpkg_divert: + path: "{{ foobarrc }}" + state: present + register: diversion_0 + check_mode: true + +- name: "divert foobarrc (must report a change)" + dpkg_divert: + path: "{{ foobarrc }}" + state: present + register: diversion_1 + + +- name: "divert foobarrc (must NOT report a change, idempotency)" + dpkg_divert: + path: "{{ foobarrc }}" + state: present + register: diversion_2 + +- name: "divert foobarrc (check mode, must NOT report a change, idempotency)" + dpkg_divert: + path: "{{ foobarrc }}" + state: present + register: diversion_3 + check_mode: true + + +# Ensure that 'rename' has no effect when state is not changed + +- name: "divert foobarrc (rename, must NOT report a change, idempotency)" + dpkg_divert: + path: "{{ foobarrc }}" + state: present + rename: yes + register: diversion_4 + +- name: "divert foobarrc (check mode, rename, must NOT report a change, idempotency)" + dpkg_divert: + path: "{{ foobarrc }}" + state: present + rename: yes + register: diversion_5 + check_mode: true + + +# Check results + +- name: "stat foobarrc (must still be there)" + stat: + path: "{{ foobarrc }}" + register: diversion_6 + +- name: "stat foobarrc.distrib (must not exist)" + stat: + path: "{{ foobarrc_distrib }}" + register: diversion_7 + +- name: "assert that results of test 01 are as expected" + assert: + that: + - diversion_0 is changed + - diversion_1 is changed + - diversion_2 is not changed + - diversion_3 is not changed + - diversion_4 is not changed + - diversion_5 is not changed + - diversion_6.stat.exists + - diversion_6.stat.checksum == foobarrc_oldsha1 + - not diversion_7.stat.exists + - diversion_0.diversion == diversion_1.diversion + - diversion_2.diversion == diversion_3.diversion + - diversion_4.diversion == diversion_5.diversion + - diversion_0.commands == diversion_1.commands + - diversion_2.commands == diversion_3.commands + - diversion_4.commands == diversion_5.commands + quiet: yes + + +################################################################################ +# TEST 02: state=absent + +- name: "remove diversion for foobarrc (check mode, must report a change)" + dpkg_divert: + path: "{{ foobarrc }}" + state: absent + register: diversion_0 + check_mode: true + +- name: "remove diversion for foobarrc (must report a change)" + dpkg_divert: + path: "{{ foobarrc }}" + state: absent + register: diversion_1 + + +- name: "remove diversion for foobarrc (must NOT report a change, idempotency)" + dpkg_divert: + path: "{{ foobarrc }}" + state: absent + register: diversion_2 + +- name: "remove diversion for foobarrc (check mode, must NOT report a change, idempotency)" + dpkg_divert: + path: "{{ foobarrc }}" + state: absent + register: diversion_3 + check_mode: true + + +# Check results + +- name: "stat foobarrc (must still be there)" + stat: + path: "{{ foobarrc }}" + register: diversion_4 + +- name: "stat foobarrc.distrib (must not exist)" + stat: + path: "{{ foobarrc_distrib }}" + register: diversion_5 + +- name: "assert that results of test 02 are as expected" + assert: + that: + - diversion_0 is changed + - diversion_1 is changed + - diversion_2 is not changed + - diversion_3 is not changed + - diversion_4.stat.exists + - diversion_4.stat.checksum == foobarrc_oldsha1 + - not diversion_5.stat.exists + - diversion_0.diversion == diversion_1.diversion + - diversion_2.diversion == diversion_3.diversion + - diversion_0.commands == diversion_1.commands + - diversion_2.commands == diversion_3.commands + quiet: yes + + +################################################################################ +# TEST 03: holder=ansible + +- name: "create foobarrc diversion with defaults" + dpkg_divert: + path: "{{ foobarrc }}" + + +- name: "update foobarrc diversion holder (check mode, must report a change)" + dpkg_divert: + path: "{{ foobarrc }}" + holder: "ansible" + register: diversion_0 + check_mode: yes + +- name: "update foobarrc diversion holder (must report a change)" + dpkg_divert: + path: "{{ foobarrc }}" + holder: "ansible" + register: diversion_1 + + +- name: "update foobarrc diversion holder (must NOT report a change, idempotency)" + dpkg_divert: + path: "{{ foobarrc }}" + holder: "ansible" + register: diversion_2 + +- name: "update foobarrc diversion holder (check mode, must NOT report a change, idempotency)" + dpkg_divert: + path: "{{ foobarrc }}" + holder: "ansible" + register: diversion_3 + check_mode: yes + + +# Check results + +- name: "stat foobarrc (must still be there)" + stat: + path: "{{ foobarrc }}" + register: diversion_4 + +- name: "stat foobarrc.distrib (must not exist)" + stat: + path: "{{ foobarrc_distrib }}" + register: diversion_5 + +- name: "assert that results of test 03 are as expected" + assert: + that: + - diversion_0 is changed + - diversion_1 is changed + - diversion_2 is not changed + - diversion_3 is not changed + - diversion_4.stat.exists + - diversion_4.stat.checksum == foobarrc_oldsha1 + - not diversion_5.stat.exists + - diversion_0.diversion == diversion_1.diversion + - diversion_2.diversion == diversion_3.diversion + - diversion_0.commands == diversion_1.commands + - diversion_2.commands == diversion_3.commands + quiet: yes + +- name: "remove foobarrc diversion" + dpkg_divert: + path: "{{ foobarrc }}" + state: absent + + +################################################################################ +# TEST 04: divert=/etc/foobarrc.ansible + +- name: "create foobarrc diversion with defaults" + dpkg_divert: + path: "{{ foobarrc }}" + + +- name: "update foobarrc divert path (check mode, must report a change)" + dpkg_divert: + path: "{{ foobarrc }}" + divert: "{{ foobarrc_ansible }}" + register: diversion_0 + check_mode: yes + +- name: "update foobarrc divert path (must report a change)" + dpkg_divert: + path: "{{ foobarrc }}" + divert: "{{ foobarrc_ansible }}" + register: diversion_1 + + +- name: "update foobarrc divert path (must NOT report a change, idempotency)" + dpkg_divert: + path: "{{ foobarrc }}" + divert: "{{ foobarrc_ansible }}" + register: diversion_2 + +- name: "update foobarrc divert path (check mode, must NOT report a change, idempotency)" + dpkg_divert: + path: "{{ foobarrc }}" + divert: "{{ foobarrc_ansible }}" + register: diversion_3 + check_mode: yes + + +# Check results + +- name: "stat foobarrc (must still be there)" + stat: + path: "{{ foobarrc }}" + register: diversion_4 + +- name: "stat foobarrc.ansible (must not exist)" + stat: + path: "{{ foobarrc_ansible }}" + register: diversion_5 + +- name: "assert that results of test 04 are as expected" + assert: + that: + - diversion_0 is changed + - diversion_1 is changed + - diversion_2 is not changed + - diversion_3 is not changed + - diversion_4.stat.exists + - diversion_4.stat.checksum == foobarrc_oldsha1 + - not diversion_5.stat.exists + - diversion_0.diversion == diversion_1.diversion + - diversion_2.diversion == diversion_3.diversion + - diversion_0.commands == diversion_1.commands + - diversion_2.commands == diversion_3.commands + quiet: yes + +- name: "remove foobarrc diversion" + dpkg_divert: + path: "{{ foobarrc }}" + state: absent diff --git a/tests/integration/targets/dpkg_divert/tasks/tests/02-rename.yml b/tests/integration/targets/dpkg_divert/tasks/tests/02-rename.yml new file mode 100644 index 0000000000..69a46a8a20 --- /dev/null +++ b/tests/integration/targets/dpkg_divert/tasks/tests/02-rename.yml @@ -0,0 +1,380 @@ +--- +################################################################################ +# TEST 05: rename=yes, state=present + +- name: "create diversion for foobarrc (check mode, rename, must report a change)" + dpkg_divert: + path: "{{ foobarrc }}" + rename: yes + register: diversion_0 + check_mode: yes + +- name: "create diversion for foobarrc (rename, must report a change)" + dpkg_divert: + path: "{{ foobarrc }}" + rename: yes + register: diversion_1 + + +- name: "create diversion for foobarrc (rename, must NOT report a change, idempotency)" + dpkg_divert: + path: "{{ foobarrc }}" + rename: yes + register: diversion_2 + +- name: "create diversion for foobarrc (check mode, rename, must NOT report a change, idempotency)" + dpkg_divert: + path: "{{ foobarrc }}" + rename: yes + register: diversion_3 + check_mode: yes + + +# Get results + +- name: "stat foobarrc (must not exist)" + stat: + path: "{{ foobarrc }}" + register: diversion_4 + +- name: "stat foobarrc.distrib (must exist)" + stat: + path: "{{ foobarrc_distrib }}" + register: diversion_5 + +- name: "assert that results of test 05 are as expected" + assert: + that: + - diversion_0 is changed + - diversion_1 is changed + - diversion_2 is not changed + - diversion_3 is not changed + - not diversion_4.stat.exists + - diversion_5.stat.exists + - diversion_5.stat.checksum == foobarrc_oldsha1 + - diversion_0.diversion == diversion_1.diversion + - diversion_2.diversion == diversion_3.diversion + - diversion_0.commands == diversion_1.commands + - diversion_2.commands == diversion_3.commands + quiet: yes + + +################################################################################ +# TEST 06: rename=yes, state=absent + +- name: "remove diversion for foobarrc (check mode, rename, must report a change)" + dpkg_divert: + path: "{{ foobarrc }}" + rename: yes + state: absent + register: diversion_0 + check_mode: yes + +- name: "remove diversion for foobarrc (rename, must report a change)" + dpkg_divert: + path: "{{ foobarrc }}" + rename: yes + state: absent + register: diversion_1 + + +- name: "remove diversion for foobarrc (rename, must NOT report a change, idempotency)" + dpkg_divert: + path: "{{ foobarrc }}" + rename: yes + state: absent + register: diversion_2 + +- name: "remove diversion for foobarrc (check mode, rename, must NOT report a change, idempotency)" + dpkg_divert: + path: "{{ foobarrc }}" + rename: yes + state: absent + register: diversion_3 + check_mode: yes + + +# Check results + +- name: "stat foobarrc (must exist)" + stat: + path: "{{ foobarrc }}" + register: diversion_4 + +- name: "stat foobarrc.distrib (must not exist)" + stat: + path: "{{ foobarrc_distrib }}" + register: diversion_5 + +- name: "assert that results of test 06 are as expected" + assert: + that: + - diversion_0 is changed + - diversion_1 is changed + - diversion_2 is not changed + - diversion_3 is not changed + - diversion_4.stat.exists + - diversion_4.stat.checksum == foobarrc_oldsha1 + - not diversion_5.stat.exists + - diversion_0.diversion == diversion_1.diversion + - diversion_2.diversion == diversion_3.diversion + - diversion_0.commands == diversion_1.commands + - diversion_2.commands == diversion_3.commands + quiet: yes + + +################################################################################ +# TEST 07: rename=yes, force=yes, state=present + +- name: "create foobarrc.distrib for tests" + copy: + dest: "{{ foobarrc_distrib }}" + content: "{{ foobarrc_oldtext }}" + + +- name: "create diversion for foobarrc (check mode, rename, must fail)" + dpkg_divert: + path: "{{ foobarrc }}" + rename: yes + register: diversion_0 + ignore_errors: yes + check_mode: yes + +- name: "create diversion for foobarrc (rename, must fail)" + dpkg_divert: + path: "{{ foobarrc }}" + rename: yes + register: diversion_1 + ignore_errors: yes + + +- name: "create diversion for foobarrc (check mode, force rename, must report a change)" + dpkg_divert: + path: "{{ foobarrc }}" + rename: yes + force: yes + register: diversion_2 + check_mode: yes + +- name: "create diversion for foobarrc (force rename, must report a change)" + dpkg_divert: + path: "{{ foobarrc }}" + rename: yes + force: yes + register: diversion_3 + + +- name: "create diversion for foobarrc (force rename, must NOT report a change, idempotency)" + dpkg_divert: + path: "{{ foobarrc }}" + rename: yes + force: yes + register: diversion_4 + +- name: "create diversion for foobarrc (check mode, force rename, must NOT report a change, idempotency)" + dpkg_divert: + path: "{{ foobarrc }}" + rename: yes + force: yes + register: diversion_5 + check_mode: yes + + +# Check results + +- name: "stat foobarrc (must not exist)" + stat: + path: "{{ foobarrc }}" + register: diversion_6 + +- name: "stat foobarrc.distrib (must exist)" + stat: + path: "{{ foobarrc_distrib }}" + register: diversion_7 + +- name: "assert that results of test 07 are as expected" + assert: + that: + - diversion_0 is failed + - diversion_1 is failed + - diversion_2 is changed + - diversion_3 is changed + - diversion_4 is not changed + - diversion_5 is not changed + - not diversion_6.stat.exists + - diversion_7.stat.exists + - diversion_7.stat.checksum == foobarrc_oldsha1 + - diversion_0 == diversion_1 + - diversion_2.diversion == diversion_3.diversion + - diversion_4.diversion == diversion_5.diversion + - diversion_2.commands == diversion_3.commands + - diversion_4.commands == diversion_5.commands + quiet: yes + + +################################################################################ +# TEST 08: state=present, update an existing divert path + +- name: "create foobarrc with new contents for tests" + copy: + dest: "{{ foobarrc }}" + content: "{{ foobarrc_newtext }}" + + +- name: "create diversion for foobarrc (check mode, update divert path, must report a change)" + dpkg_divert: + path: "{{ foobarrc }}" + divert: "{{ foobarrc_ansible }}" + register: diversion_0 + check_mode: yes + +- name: "create diversion for foobarrc (update divert path, must report a change)" + dpkg_divert: + path: "{{ foobarrc }}" + divert: "{{ foobarrc_ansible }}" + register: diversion_1 + + +- name: "create diversion for foobarrc (update divert path, must NOT report a change, idempotency)" + dpkg_divert: + path: "{{ foobarrc }}" + divert: "{{ foobarrc_ansible }}" + register: diversion_2 + +- name: "create diversion for foobarrc (check mode, update divert path, must NOT report a change, idempotency)" + dpkg_divert: + path: "{{ foobarrc }}" + divert: "{{ foobarrc_ansible }}" + register: diversion_3 + check_mode: yes + + +# Check results + +- name: "stat foobarrc (must exist)" + stat: + path: "{{ foobarrc }}" + register: diversion_4 + +- name: "stat foobarrc.ansible (must exist)" + stat: + path: "{{ foobarrc_ansible }}" + register: diversion_5 + +- name: "stat foobarrc.distrib (must not exist)" + stat: + path: "{{ foobarrc_distrib }}" + register: diversion_6 + +- name: "assert that results of test 08 are as expected" + assert: + that: + - diversion_0 is changed + - diversion_1 is changed + - diversion_2 is not changed + - diversion_3 is not changed + - diversion_4.stat.exists + - diversion_4.stat.checksum == foobarrc_newsha1 + - diversion_5.stat.exists + - diversion_5.stat.checksum == foobarrc_oldsha1 + - not diversion_6.stat.exists + - diversion_0.diversion == diversion_1.diversion + - diversion_2.diversion == diversion_3.diversion + - diversion_0.commands == diversion_1.commands + - diversion_2.commands == diversion_3.commands + quiet: yes + + +################################################################################ +# TEST 09: rename=yes, force=yes, state=absent + +- name: "remove diversion for foobarrc (check mode, rename, must fail)" + dpkg_divert: + path: "{{ foobarrc }}" + state: absent + rename: yes + register: diversion_0 + ignore_errors: yes + check_mode: yes + +- name: "remove diversion for foobarrc (rename, must fail)" + dpkg_divert: + path: "{{ foobarrc }}" + state: absent + rename: yes + register: diversion_1 + ignore_errors: yes + + +- name: "remove diversion for foobarrc (check mode, force rename, must report a change)" + dpkg_divert: + path: "{{ foobarrc }}" + state: absent + rename: yes + force: yes + register: diversion_2 + check_mode: yes + +- name: "remove diversion for foobarrc (force rename, must report a change)" + dpkg_divert: + path: "{{ foobarrc }}" + state: absent + rename: yes + force: yes + register: diversion_3 + + +- name: "remove diversion for foobarrc (force rename, must NOT report a change, idempotency)" + dpkg_divert: + path: "{{ foobarrc }}" + state: absent + rename: yes + force: yes + register: diversion_4 + +- name: "remove diversion for foobarrc (check mode, force rename, must NOT report a change, idempotency)" + dpkg_divert: + path: "{{ foobarrc }}" + state: absent + rename: yes + force: yes + register: diversion_5 + check_mode: yes + + +# Check results + +- name: "stat foobarrc (must exist)" + stat: + path: "{{ foobarrc }}" + register: diversion_6 + +- name: "stat foobarrc.distrib (must not exist)" + stat: + path: "{{ foobarrc_distrib }}" + register: diversion_7 + +- name: "stat foobarrc.ansible (must not exist)" + stat: + path: "{{ foobarrc_ansible }}" + register: diversion_8 + +- name: "assert that results of test 09 are as expected" + assert: + that: + - diversion_0 is failed + - diversion_1 is failed + - diversion_2 is changed + - diversion_3 is changed + - diversion_4 is not changed + - diversion_5 is not changed + - diversion_6.stat.exists + - diversion_6.stat.checksum == foobarrc_oldsha1 + - not diversion_7.stat.exists + - not diversion_8.stat.exists + - diversion_0 == diversion_1 + - diversion_2.diversion == diversion_3.diversion + - diversion_4.diversion == diversion_5.diversion + - diversion_2.commands == diversion_3.commands + - diversion_4.commands == diversion_5.commands + quiet: yes