#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright (c) 2018, Florian Paul Azim Hoberg (@gyptazy) # # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # SPDX-License-Identifier: GPL-3.0-or-later from __future__ import absolute_import, division, print_function __metaclass__ = type DOCUMENTATION = r''' --- module: yum_versionlock version_added: 2.0.0 short_description: Locks / unlocks a installed package(s) from being updated by yum package manager description: - This module adds installed packages to yum versionlock to prevent the package(s) from being updated. extends_documentation_fragment: - community.general.attributes attributes: check_mode: support: full diff_mode: support: none options: name: description: - Package name or a list of package names with optional version or wildcards. - Specifying versions is supported since community.general 7.2.0. type: list required: true elements: str state: description: - If state is V(present), package(s) will be added to yum versionlock list. - If state is V(absent), package(s) will be removed from yum versionlock list. choices: [ 'absent', 'present' ] type: str default: present notes: - Requires yum-plugin-versionlock package on the remote node. requirements: - yum - yum-versionlock author: - Florian Paul Azim Hoberg (@gyptazy) - Amin Vakil (@aminvakil) ''' EXAMPLES = r''' - name: Prevent Apache / httpd from being updated community.general.yum_versionlock: state: present name: - httpd - name: Prevent Apache / httpd version 2.4.57-2 from being updated community.general.yum_versionlock: state: present name: - httpd-0:2.4.57-2.el9 - name: Prevent multiple packages from being updated community.general.yum_versionlock: state: present name: - httpd - nginx - haproxy - curl - name: Remove lock from Apache / httpd to be updated again community.general.yum_versionlock: state: absent name: httpd ''' RETURN = r''' packages: description: A list of package(s) in versionlock list. returned: success type: list elements: str sample: [ 'httpd' ] state: description: State of package(s). returned: success type: str sample: present ''' import re from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.common.text.converters import to_native from fnmatch import fnmatch # on DNF-based distros, yum is a symlink to dnf, so we try to handle their different entry formats. NEVRA_RE_YUM = re.compile(r'^(?P!)?(?P\d+):(?P.+)-' r'(?P.+)-(?P.+)\.(?P.+)$') NEVRA_RE_DNF = re.compile(r"^(?P!)?(?P.+)-(?P\d+):(?P.+)-" r"(?P.+)\.(?P.+)$") class YumVersionLock: def __init__(self, module): self.module = module self.params = module.params self.yum_bin = module.get_bin_path('yum', required=True) def get_versionlock_packages(self): """ Get an overview of all packages on yum versionlock """ rc, out, err = self.module.run_command([self.yum_bin, "versionlock", "list"]) if rc == 0: return out elif rc == 1 and 'o such command:' in err: self.module.fail_json(msg="Error: Please install rpm package yum-plugin-versionlock : " + to_native(err) + to_native(out)) self.module.fail_json(msg="Error: " + to_native(err) + to_native(out)) def ensure_state(self, packages, command): """ Ensure packages state """ rc, out, err = self.module.run_command([self.yum_bin, "-q", "versionlock", command] + packages) # If no package can be found this will be written on stdout with rc 0 if 'No package found for' in out: self.module.fail_json(msg=out) if rc == 0: return True self.module.fail_json(msg="Error: " + to_native(err) + to_native(out)) def match(entry, name): match = False m = NEVRA_RE_YUM.match(entry) if not m: m = NEVRA_RE_DNF.match(entry) if not m: return False if fnmatch(m.group("name"), name): match = True if entry.rstrip('.*') == name: match = True return match def main(): """ start main program to add/delete a package to yum versionlock """ module = AnsibleModule( argument_spec=dict( state=dict(default='present', choices=['present', 'absent']), name=dict(required=True, type='list', elements='str'), ), supports_check_mode=True ) state = module.params['state'] packages = module.params['name'] changed = False yum_v = YumVersionLock(module) # Get an overview of all packages that have a version lock versionlock_packages = yum_v.get_versionlock_packages() # Ensure versionlock state of packages packages_list = [] if state in ('present', ): command = 'add' for single_pkg in packages: if not any(match(pkg, single_pkg) for pkg in versionlock_packages.split()): packages_list.append(single_pkg) if packages_list: if module.check_mode: changed = True else: changed = yum_v.ensure_state(packages_list, command) elif state in ('absent', ): command = 'delete' for single_pkg in packages: if any(match(pkg, single_pkg) for pkg in versionlock_packages.split()): packages_list.append(single_pkg) if packages_list: if module.check_mode: changed = True else: changed = yum_v.ensure_state(packages_list, command) module.exit_json( changed=changed, meta={ "packages": packages, "state": state } ) if __name__ == '__main__': main()