#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Florian Paul Azim Hoberg <florian.hoberg@credativ.de>
#
# 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.
options:
  name:
    description:
      - Package name or a list of package names with optional wildcards.
    type: list
    required: true
    elements: str
  state:
    description:
    - If state is C(present), package(s) will be added to yum versionlock list.
    - If state is C(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.
    - Supports C(check_mode).
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 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
    package: 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<exclude>!)?(?P<epoch>\d+):(?P<name>.+)-'
                          r'(?P<version>.+)-(?P<release>.+)\.(?P<arch>.+)$')
NEVRA_RE_DNF = re.compile(r"^(?P<exclude>!)?(?P<name>.+)-(?P<epoch>\d+):(?P<version>.+)-"
                          r"(?P<release>.+)\.(?P<arch>.+)$")


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 rc == 0:
            return True
        self.module.fail_json(msg="Error: " + to_native(err) + to_native(out))


def match(entry, name):
    m = NEVRA_RE_YUM.match(entry)
    if not m:
        m = NEVRA_RE_DNF.match(entry)
    if not m:
        return False
    return fnmatch(m.group("name"), name)


def main():
    """ start main program to add/remove 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()
