diff --git a/lib/ansible/modules/packaging/os/apt.py b/lib/ansible/modules/packaging/os/apt.py index 8f05ba2a3ed..42332077c6a 100644 --- a/lib/ansible/modules/packaging/os/apt.py +++ b/lib/ansible/modules/packaging/os/apt.py @@ -109,6 +109,15 @@ options: type: bool default: 'no' version_added: "2.4" + policy_rc_d: + description: + - Force the exit code of /usr/sbin/policy-rc.d. + - For example, if I(policy_rc_d=101) the installed package will not trigger a service start. + - If /usr/sbin/policy-rc.d already exist, it is backed up and restored after the package installation. + - If C(null), the /usr/sbin/policy-rc.d isn't created/changed. + type: int + default: null + version_added: "2.8" only_upgrade: description: - Only upgrade a package if it is already installed. @@ -261,8 +270,10 @@ import datetime import fnmatch import itertools import os +import shutil import re import sys +import tempfile import time from ansible.module_utils.basic import AnsibleModule @@ -309,6 +320,83 @@ else: PYTHON_APT = 'python3-apt' +class PolicyRcD(object): + """ + This class is a context manager for the /usr/sbin/policy-rc.d file. + It allow the user to prevent dpkg to start the corresponding service when installing + a package. + https://people.debian.org/~hmh/invokerc.d-policyrc.d-specification.txt + """ + + def __init__(self, module): + # we need the module for later use (eg. fail_json) + self.m = module + + # if policy_rc_d is null then we don't need to modify policy-rc.d + if self.m.params['policy_rc_d'] is None: + return + + # if the /usr/sbin/policy-rc.d already exist + # we will back it up during package installation + # then restore it + if os.path.exists('/usr/sbin/policy-rc.d'): + self.backup_dir = tempfile.mkdtemp(prefix="ansible") + else: + self.backup_dir = None + + def __enter__(self): + """ + This method will be call when we enter the context, before we call `apt-get …` + """ + + # if policy_rc_d is null then we don't need to modify policy-rc.d + if self.m.params['policy_rc_d'] is None: + return + + # if the /usr/sbin/policy-rc.d already exist we back it up + if self.backup_dir: + try: + shutil.move('/usr/sbin/policy-rc.d', self.backup_dir) + except: + self.m.fail_json(msg="Fail to move /usr/sbin/policy-rc.d to %s" % self.backup_dir) + + # we write /usr/sbin/policy-rc.d so it always exit with code policy_rc_d + try: + with open('/usr/sbin/policy-rc.d', 'w') as policy_rc_d: + policy_rc_d.write('#!/bin/sh\nexit %d\n' % self.m.params['policy_rc_d']) + + os.chmod('/usr/sbin/policy-rc.d', 0o0755) + except: + self.m.fail_json(msg="Failed to create or chmod /usr/sbin/policy-rc.d") + + def __exit__(self, type, value, traceback): + """ + This method will be call when we enter the context, before we call `apt-get …` + """ + + # if policy_rc_d is null then we don't need to modify policy-rc.d + if self.m.params['policy_rc_d'] is None: + return + + if self.backup_dir: + # if /usr/sbin/policy-rc.d already exists before the call to __enter__ + # we restore it (from the backup done in __enter__) + try: + shutil.move(os.path.join(self.backup_dir, 'policy-rc.d'), + '/usr/sbin/policy-rc.d') + os.rmdir(self.tmpdir_name) + except: + self.m.fail_json(msg="Fail to move back %s to /usr/sbin/policy-rc.d" + % os.path.join(self.backup_dir, 'policy-rc.d')) + else: + # if they wheren't any /usr/sbin/policy-rc.d file before the call to __enter__ + # we just remove the file + try: + os.remove('/usr/sbin/policy-rc.d') + except: + self.m.fail_json(msg="Fail to remove /usr/sbin/policy-rc.d (after package manipulation)") + + def package_split(pkgspec): parts = pkgspec.split('=', 1) version = None @@ -585,7 +673,9 @@ def install(m, pkgspec, cache, upgrade=False, default_release=None, if allow_unauthenticated: cmd += " --allow-unauthenticated" - rc, out, err = m.run_command(cmd) + with PolicyRcD(m): + rc, out, err = m.run_command(cmd) + if m._diff: diff = parse_diff(out) else: @@ -674,7 +764,10 @@ def install_deb(m, debs, cache, force, install_recommends, allow_unauthenticated options += " --force-all" cmd = "dpkg %s -i %s" % (options, " ".join(pkgs_to_install)) - rc, out, err = m.run_command(cmd) + + with PolicyRcD(m): + rc, out, err = m.run_command(cmd) + if "stdout" in retvals: stdout = retvals["stdout"] + out else: @@ -734,7 +827,9 @@ def remove(m, pkgspec, cache, purge=False, force=False, cmd = "%s -q -y %s %s %s %s %s remove %s" % (APT_GET_CMD, dpkg_options, purge, force_yes, autoremove, check_arg, packages) - rc, out, err = m.run_command(cmd) + with PolicyRcD(m): + rc, out, err = m.run_command(cmd) + if m._diff: diff = parse_diff(out) else: @@ -767,7 +862,9 @@ def cleanup(m, purge=False, force=False, operation=None, cmd = "%s -y %s %s %s %s %s" % (APT_GET_CMD, dpkg_options, purge, force_yes, operation, check_arg) - rc, out, err = m.run_command(cmd) + with PolicyRcD(m): + rc, out, err = m.run_command(cmd) + if m._diff: diff = parse_diff(out) else: @@ -840,7 +937,9 @@ def upgrade(m, mode="yes", force=False, default_release=None, if default_release: cmd += " -t '%s'" % (default_release,) - rc, out, err = m.run_command(cmd, prompt_regex=prompt_regex) + with PolicyRcD(m): + rc, out, err = m.run_command(cmd, prompt_regex=prompt_regex) + if m._diff: diff = parse_diff(out) else: @@ -917,6 +1016,7 @@ def main(): dpkg_options=dict(type='str', default=DPKG_OPTIONS), autoremove=dict(type='bool', default=False), autoclean=dict(type='bool', default=False), + policy_rc_d=dict(type='int', default=None), only_upgrade=dict(type='bool', default=False), force_apt_get=dict(type='bool', default=False), allow_unauthenticated=dict(type='bool', default=False, aliases=['allow-unauthenticated']), diff --git a/test/integration/targets/apt/tasks/apt.yml b/test/integration/targets/apt/tasks/apt.yml index bd8f225a546..b0131682dd2 100644 --- a/test/integration/targets/apt/tasks/apt.yml +++ b/test/integration/targets/apt/tasks/apt.yml @@ -258,3 +258,62 @@ assert: that: - apt_result is not changed + +# check policy_rc_d parameter + +- name: Install unscd but forbid service start + apt: + name: unscd + policy_rc_d: 101 + +- name: Stop unscd service + service: + name: unscd + state: stopped + register: service_unscd_stop + +- name: unscd service shouldn't have been stopped by previous task + assert: + that: service_unscd_stop is not changed + +- name: Uninstall unscd + apt: + name: unscd + policy_rc_d: 101 + +- name: Create incorrect /usr/sbin/policy-rc.d + copy: + dest: /usr/sbin/policy-rc.d + content: apt integration test + mode: 0755 + +- name: Install unscd but forbid service start + apt: + name: unscd + policy_rc_d: 101 + +- name: Stop unscd service + service: + name: unscd + state: stopped + register: service_unscd_stop + +- name: unscd service shouldn't have been stopped by previous task + assert: + that: service_unscd_stop is not changed + +- name: Create incorrect /usr/sbin/policy-rc.d + copy: + dest: /usr/sbin/policy-rc.d + content: apt integration test + mode: 0755 + register: policy_rc_d + +- name: Check if /usr/sbin/policy-rc.d was correctly backed-up during unscd install + assert: + that: policy_rc_d is not changed + +- name: Delete /usr/sbin/policy-rc.d + file: + path: /usr/sbin/policy-rc.d + state: absent