.. versionadded:: 2014.1.0
    .. versionchanged:: 2014.7.0
        All available versions of each package are now returned. This required
        a slight modification to the structure of the return dict. The return
        data shown below reflects the updated return dict structure. Note that
        packages which are version-locked using :py:mod:`pkg.hold
        <salt.modules.yumpkg.hold>` will only show the currently-installed
        version, as locking a package will make other versions appear
        unavailable to yum/dnf.
    .. versionchanged:: 2017.7.0
        By default, the versions for each package are no longer organized by
        repository. To get results organized by repository, use
        ``byrepo=True``.

    Returns all available packages. Optionally, package names (and name globs)
    can be passed and the results will be filtered to packages matching those
    names. This is recommended as it speeds up the function considerably.

    .. warning::
        Running this function on RHEL/CentOS 6 and earlier will be more
        resource-intensive, as the version of yum that ships with older
        RHEL/CentOS has no yum subcommand for listing packages from a
        repository. Thus, a ``yum list installed`` and ``yum list available``
        are run, which generates a lot of output, which must then be analyzed
        to determine which package information to include in the return data.

    This function can be helpful in discovering the version or repo to specify
    in a :mod:`pkg.installed <salt.states.pkg.installed>` state.

    The return data will be a dictionary mapping package names to a list of
    version numbers, ordered from newest to oldest. If ``byrepo`` is set to
    ``True``, then the return dictionary will contain repository names at the
    top level, and each repository will map packages to lists of version
    numbers. For example:

    .. code-block:: python

        # With byrepo=False (default)
        {
            'bash': ['4.1.2-15.el6_5.2',
                     '4.1.2-15.el6_5.1',
                     '4.1.2-15.el6_4'],
            'kernel': ['2.6.32-431.29.2.el6',
                       '2.6.32-431.23.3.el6',
                       '2.6.32-431.20.5.el6',
                       '2.6.32-431.20.3.el6',
                       '2.6.32-431.17.1.el6',
                       '2.6.32-431.11.2.el6',
                       '2.6.32-431.5.1.el6',
                       '2.6.32-431.3.1.el6',
                       '2.6.32-431.1.2.0.1.el6',
                       '2.6.32-431.el6']
        }
        # With byrepo=True
        {
            'base': {
                'bash': ['4.1.2-15.el6_4'],
                'kernel': ['2.6.32-431.el6']
            },
            'updates': {
                'bash': ['4.1.2-15.el6_5.2', '4.1.2-15.el6_5.1'],
                'kernel': ['2.6.32-431.29.2.el6',
                           '2.6.32-431.23.3.el6',
                           '2.6.32-431.20.5.el6',
                           '2.6.32-431.20.3.el6',
                           '2.6.32-431.17.1.el6',
                           '2.6.32-431.11.2.el6',
                           '2.6.32-431.5.1.el6',
                           '2.6.32-431.3.1.el6',
                           '2.6.32-431.1.2.0.1.el6']
            }
        }

    fromrepo : None
        Only include results from the specified repo(s). Multiple repos can be
        specified, comma-separated.

    enablerepo (ignored if ``fromrepo`` is specified)
        Specify a disabled package repository (or repositories) to enable.
        (e.g., ``yum --enablerepo='somerepo'``)

        .. versionadded:: 2017.7.0

    disablerepo (ignored if ``fromrepo`` is specified)
        Specify an enabled package repository (or repositories) to disable.
        (e.g., ``yum --disablerepo='somerepo'``)

        .. versionadded:: 2017.7.0

    byrepo : False
        When ``True``, the return data for each package will be organized by
        repository.

        .. versionadded:: 2017.7.0

    cacheonly : False
        When ``True``, the repo information will be retrieved from the cached
        repo metadata. This is equivalent to passing the ``-C`` option to
        yum/dnf.

        .. versionadded:: 2017.7.0

    setopt
        A comma-separated or Python list of key=value options. This list will
        be expanded and ``--setopt`` prepended to each in the yum/dnf command
        that is run.

        .. versionadded:: 2019.2.0

    CLI Examples:

    .. code-block:: bash

        salt '*' pkg.list_repo_pkgs
        salt '*' pkg.list_repo_pkgs foo bar baz
        salt '*' pkg.list_repo_pkgs 'samba4*' fromrepo=base,updates
        salt '*' pkg.list_repo_pkgs 'python2-*' byrepo=True

def list_repo_pkgs(*args, **kwargs):
    '''
    .. versionadded:: 2014.1.0
    .. versionchanged:: 2014.7.0
        All available versions of each package are now returned. This required
        a slight modification to the structure of the return dict. The return
        data shown below reflects the updated return dict structure. Note that
        packages which are version-locked using :py:mod:`pkg.hold
        <salt.modules.yumpkg.hold>` will only show the currently-installed
        version, as locking a package will make other versions appear
        unavailable to yum/dnf.
    .. versionchanged:: 2017.7.0
        By default, the versions for each package are no longer organized by
        repository. To get results organized by repository, use
        ``byrepo=True``.

    Returns all available packages. Optionally, package names (and name globs)
    can be passed and the results will be filtered to packages matching those
    names. This is recommended as it speeds up the function considerably.

    .. warning::
        Running this function on RHEL/CentOS 6 and earlier will be more
        resource-intensive, as the version of yum that ships with older
        RHEL/CentOS has no yum subcommand for listing packages from a
        repository. Thus, a ``yum list installed`` and ``yum list available``
        are run, which generates a lot of output, which must then be analyzed
        to determine which package information to include in the return data.

    This function can be helpful in discovering the version or repo to specify
    in a :mod:`pkg.installed <salt.states.pkg.installed>` state.

    The return data will be a dictionary mapping package names to a list of
    version numbers, ordered from newest to oldest. If ``byrepo`` is set to
    ``True``, then the return dictionary will contain repository names at the
    top level, and each repository will map packages to lists of version
    numbers. For example:

    .. code-block:: python

        # With byrepo=False (default)
        {
            'bash': ['4.1.2-15.el6_5.2',
                     '4.1.2-15.el6_5.1',
                     '4.1.2-15.el6_4'],
            'kernel': ['2.6.32-431.29.2.el6',
                       '2.6.32-431.23.3.el6',
                       '2.6.32-431.20.5.el6',
                       '2.6.32-431.20.3.el6',
                       '2.6.32-431.17.1.el6',
                       '2.6.32-431.11.2.el6',
                       '2.6.32-431.5.1.el6',
                       '2.6.32-431.3.1.el6',
                       '2.6.32-431.1.2.0.1.el6',
                       '2.6.32-431.el6']
        }
        # With byrepo=True
        {
            'base': {
                'bash': ['4.1.2-15.el6_4'],
                'kernel': ['2.6.32-431.el6']
            },
            'updates': {
                'bash': ['4.1.2-15.el6_5.2', '4.1.2-15.el6_5.1'],
                'kernel': ['2.6.32-431.29.2.el6',
                           '2.6.32-431.23.3.el6',
                           '2.6.32-431.20.5.el6',
                           '2.6.32-431.20.3.el6',
                           '2.6.32-431.17.1.el6',
                           '2.6.32-431.11.2.el6',
                           '2.6.32-431.5.1.el6',
                           '2.6.32-431.3.1.el6',
                           '2.6.32-431.1.2.0.1.el6']
            }
        }

    fromrepo : None
        Only include results from the specified repo(s). Multiple repos can be
        specified, comma-separated.

    enablerepo (ignored if ``fromrepo`` is specified)
        Specify a disabled package repository (or repositories) to enable.
        (e.g., ``yum --enablerepo='somerepo'``)

        .. versionadded:: 2017.7.0

    disablerepo (ignored if ``fromrepo`` is specified)
        Specify an enabled package repository (or repositories) to disable.
        (e.g., ``yum --disablerepo='somerepo'``)

        .. versionadded:: 2017.7.0

    byrepo : False
        When ``True``, the return data for each package will be organized by
        repository.

        .. versionadded:: 2017.7.0

    cacheonly : False
        When ``True``, the repo information will be retrieved from the cached
        repo metadata. This is equivalent to passing the ``-C`` option to
        yum/dnf.

        .. versionadded:: 2017.7.0

    setopt
        A comma-separated or Python list of key=value options. This list will
        be expanded and ``--setopt`` prepended to each in the yum/dnf command
        that is run.

        .. versionadded:: 2019.2.0

    CLI Examples:

    .. code-block:: bash

        salt '*' pkg.list_repo_pkgs
        salt '*' pkg.list_repo_pkgs foo bar baz
        salt '*' pkg.list_repo_pkgs 'samba4*' fromrepo=base,updates
        salt '*' pkg.list_repo_pkgs 'python2-*' byrepo=True
    '''
    byrepo = kwargs.pop('byrepo', False)
    cacheonly = kwargs.pop('cacheonly', False)
    fromrepo = kwargs.pop('fromrepo', '') or ''
    disablerepo = kwargs.pop('disablerepo', '') or ''
    enablerepo = kwargs.pop('enablerepo', '') or ''

    repo_arg = _get_options(fromrepo=fromrepo, **kwargs)

    if fromrepo and not isinstance(fromrepo, list):
        try:
            fromrepo = [x.strip() for x in fromrepo.split(',')]
        except AttributeError:
            fromrepo = [x.strip() for x in six.text_type(fromrepo).split(',')]

    if disablerepo and not isinstance(disablerepo, list):
        try:
            disablerepo = [x.strip() for x in disablerepo.split(',')
                           if x != '*']
        except AttributeError:
            disablerepo = [x.strip() for x in six.text_type(disablerepo).split(',')
                           if x != '*']

    if enablerepo and not isinstance(enablerepo, list):
        try:
            enablerepo = [x.strip() for x in enablerepo.split(',')
                          if x != '*']
        except AttributeError:
            enablerepo = [x.strip() for x in six.text_type(enablerepo).split(',')
                          if x != '*']

    if fromrepo:
        repos = fromrepo
    else:
        repos = [
            repo_name for repo_name, repo_info in six.iteritems(list_repos())
            if repo_name in enablerepo
            or (repo_name not in disablerepo
                and six.text_type(repo_info.get('enabled', '1')) == '1')
        ]

    ret = {}

    def _check_args(args, name):
        '''
        Do glob matching on args and return True if a match was found.
        Otherwise, return False
        '''
        for arg in args:
            if fnmatch.fnmatch(name, arg):
                return True
        return False

    def _parse_output(output, strict=False):
        for pkg in _yum_pkginfo(output):
            if strict and (pkg.repoid not in repos
                           or not _check_args(args, pkg.name)):
                continue
            repo_dict = ret.setdefault(pkg.repoid, {})
            version_list = repo_dict.setdefault(pkg.name, set())
            version_list.add(pkg.version)

    yum_version = None if _yum() != 'yum' else _LooseVersion(
                __salt__['cmd.run'](
                    ['yum', '--version'],
                    python_shell=False
                ).splitlines()[0].strip()
            )
    # Really old version of yum; does not even have --showduplicates option
    if yum_version and yum_version < _LooseVersion('3.2.13'):
        cmd_prefix = ['--quiet']
        if cacheonly:
            cmd_prefix.append('-C')
        cmd_prefix.append('list')
        for pkg_src in ('installed', 'available'):
            # Check installed packages first
            out = _call_yum(cmd_prefix + [pkg_src], ignore_retcode=True)
            if out['retcode'] == 0:
                _parse_output(out['stdout'], strict=True)
    # The --showduplicates option is added in 3.2.13, but the
    # repository-packages subcommand is only in 3.4.3 and newer
    elif yum_version and yum_version < _LooseVersion('3.4.3'):
        cmd_prefix = ['--quiet', '--showduplicates']
        if cacheonly:
            cmd_prefix.append('-C')
        cmd_prefix.append('list')
        for pkg_src in ('installed', 'available'):
            # Check installed packages first
            out = _call_yum(cmd_prefix + [pkg_src], ignore_retcode=True)
            if out['retcode'] == 0:
                _parse_output(out['stdout'], strict=True)
    else:
        for repo in repos:
            cmd = ['--quiet', '--showduplicates', 'repository-packages', repo, 'list']
            if cacheonly:
                cmd.append('-C')
            # Can't concatenate because args is a tuple, using list.extend()
            cmd.extend(args)
            out = _call_yum(cmd, ignore_retcode=True)
            if out['retcode'] != 0 and 'Error:' in out['stdout']:
                continue
            _parse_output(out['stdout'])

    if byrepo:
        for reponame in ret:
            # Sort versions newest to oldest
            for pkgname in ret[reponame]:
                sorted_versions = sorted(
                    [_LooseVersion(x) for x in ret[reponame][pkgname]],
                    reverse=True
                )
                ret[reponame][pkgname] = [x.vstring for x in sorted_versions]
        return ret
    else:
        byrepo_ret = {}
        for reponame in ret:
            for pkgname in ret[reponame]:
                byrepo_ret.setdefault(pkgname, []).extend(ret[reponame][pkgname])
        for pkgname in byrepo_ret:
            sorted_versions = sorted(
                [_LooseVersion(x) for x in byrepo_ret[pkgname]],
                reverse=True
            )
            byrepo_ret[pkgname] = [x.vstring for x in sorted_versions]
        return byrepo_ret