HEX
Server: Apache
System: Linux sg241.singhost.net 2.6.32-896.16.1.lve1.4.51.el6.x86_64 #1 SMP Wed Jan 17 13:19:23 EST 2018 x86_64
User: honghock (909)
PHP: 8.0.30
Disabled: passthru,system,shell_exec,show_source,exec,popen,proc_open
Upload Files
File: //proc/self/root/usr/lib/python2.7/site-packages/salt/modules/pkgin.py
# -*- coding: utf-8 -*-
'''
Package support for pkgin based systems, inspired from freebsdpkg module

.. important::
    If you feel that Salt should be using this module to manage packages on a
    minion, and it is using a different module (or gives an error similar to
    *'pkg.install' is not available*), see :ref:`here
    <module-provider-override>`.
'''

# Import python libs
from __future__ import absolute_import, print_function, unicode_literals
import copy
import logging
import os
import re

# Import salt libs
import salt.utils.data
import salt.utils.functools
import salt.utils.path
import salt.utils.pkg
import salt.utils.decorators as decorators
from salt.exceptions import CommandExecutionError, MinionError

# Import 3rd-party libs
from salt.ext import six

VERSION_MATCH = re.compile(r'pkgin(?:[\s]+)([\d.]+)(?:[\s]+)(?:.*)')
log = logging.getLogger(__name__)

# Define the module's virtual name
__virtualname__ = 'pkg'


@decorators.memoize
def _check_pkgin():
    '''
    Looks to see if pkgin is present on the system, return full path
    '''
    ppath = salt.utils.path.which('pkgin')
    if ppath is None:
        # pkgin was not found in $PATH, try to find it via LOCALBASE
        try:
            localbase = __salt__['cmd.run'](
               'pkg_info -Q LOCALBASE pkgin',
                output_loglevel='trace'
            )
            if localbase is not None:
                ppath = '{0}/bin/pkgin'.format(localbase)
                if not os.path.exists(ppath):
                    return None
        except CommandExecutionError:
            return None
    return ppath


@decorators.memoize
def _get_version():
    '''
    Get the pkgin version
    '''
    version_string = __salt__['cmd.run'](
        [_check_pkgin(), '-v'],
        output_loglevel='trace')
    if version_string is None:
        # Dunno why it would, but...
        return False

    version_match = VERSION_MATCH.search(version_string)
    if not version_match:
        return False

    return version_match.group(1).split('.')


@decorators.memoize
def _supports_regex():
    '''
    Check support of regexp
    '''
    return tuple([int(i) for i in _get_version()]) > (0, 5)


@decorators.memoize
def _supports_parsing():
    '''
    Check support of parsing
    '''
    return tuple([int(i) for i in _get_version()]) > (0, 6)


def __virtual__():
    '''
    Set the virtual pkg module if the os is supported by pkgin
    '''
    supported = ['NetBSD', 'SunOS', 'DragonFly', 'Minix', 'Darwin', 'SmartOS']

    if __grains__['os'] in supported and _check_pkgin():
        return __virtualname__
    return (False, 'The pkgin execution module cannot be loaded: only '
            'available on {0} systems.'.format(', '.join(supported)))


def _splitpkg(name):
    '''
    Split package name from versioned string
    '''
    # name is in the format foobar-1.0nb1, already space-splitted
    if name[0].isalnum() and name != 'No':  # avoid < > = and 'No result'
        return name.split(';', 1)[0].rsplit('-', 1)


def search(pkg_name):
    '''
    Searches for an exact match using pkgin ^package$

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.search 'mysql-server'
    '''

    pkglist = {}
    pkgin = _check_pkgin()
    if not pkgin:
        return pkglist

    if _supports_regex():
        pkg_name = '^{0}$'.format(pkg_name)

    out = __salt__['cmd.run'](
        [pkgin, 'se', pkg_name],
        output_loglevel='trace'
    )
    for line in out.splitlines():
        if line:
            match = _splitpkg(line.split()[0])
            if match:
                pkglist[match[0]] = match[1]

    return pkglist


def latest_version(*names, **kwargs):
    '''
    .. versionchanged: 2016.3.0

    Return the latest version of the named package available for upgrade or
    installation.

    If the latest version of a given package is already installed, an empty
    string will be returned for that package.

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.latest_version <package name>
        salt '*' pkg.latest_version <package1> <package2> ...
    '''

    refresh = salt.utils.data.is_true(kwargs.pop('refresh', True))

    pkglist = {}
    pkgin = _check_pkgin()
    if not pkgin:
        return pkglist

    # Refresh before looking for the latest version available
    if refresh:
        refresh_db()

    cmd_prefix = [pkgin, 'se']
    if _supports_parsing():
        cmd_prefix.insert(1, '-p')
    for name in names:
        cmd = copy.deepcopy(cmd_prefix)
        cmd.append('^{0}$'.format(name) if _supports_regex() else name)

        out = __salt__['cmd.run'](cmd, output_loglevel='trace')
        for line in out.splitlines():
            if line.startswith('No results found for'):
                return pkglist
            p = line.split(';' if _supports_parsing() else None)

            if p and p[0] in ('=:', '<:', '>:', ''):
                # These are explanation comments
                continue
            elif p:
                s = _splitpkg(p[0])
                if s:
                    if not s[0] in pkglist:
                        if len(p) > 1 and p[1] in ('<', '', '='):
                            pkglist[s[0]] = s[1]
                        else:
                            pkglist[s[0]] = ''

    if pkglist and len(names) == 1:
        if names[0] in pkglist:
            return pkglist[names[0]]
    else:
        return pkglist


# available_version is being deprecated
available_version = salt.utils.functools.alias_function(latest_version, 'available_version')


def version(*names, **kwargs):
    '''
    Returns a string representing the package version or an empty string if not
    installed. If more than one package name is specified, a dict of
    name/version pairs is returned.

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.version <package name>
        salt '*' pkg.version <package1> <package2> <package3> ...
    '''
    return __salt__['pkg_resource.version'](*names, **kwargs)


def refresh_db(force=False):
    '''
    Use pkg update to get latest pkg_summary

    force
        Pass -f so that the cache is always refreshed.

        .. versionadded:: 2018.3.0


    CLI Example:

    .. code-block:: bash

        salt '*' pkg.refresh_db
    '''
    # Remove rtag file to keep multiple refreshes from happening in pkg states
    salt.utils.pkg.clear_rtag(__opts__)
    pkgin = _check_pkgin()

    if pkgin:
        cmd = [pkgin, 'up']
        if force:
            cmd.insert(1, '-f')
        call = __salt__['cmd.run_all'](cmd, output_loglevel='trace')

        if call['retcode'] != 0:
            comment = ''
            if 'stderr' in call:
                comment += call['stderr']

            raise CommandExecutionError(comment)

    return True


def list_pkgs(versions_as_list=False, **kwargs):
    '''
    .. versionchanged: 2016.3.0

    List the packages currently installed as a dict::

        {'<package_name>': '<version>'}

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.list_pkgs
    '''
    versions_as_list = salt.utils.data.is_true(versions_as_list)
    # not yet implemented or not applicable
    if any([salt.utils.data.is_true(kwargs.get(x))
            for x in ('removed', 'purge_desired')]):
        return {}

    if 'pkg.list_pkgs' in __context__:
        if versions_as_list:
            return __context__['pkg.list_pkgs']
        else:
            ret = copy.deepcopy(__context__['pkg.list_pkgs'])
            __salt__['pkg_resource.stringify'](ret)
            return ret

    pkgin = _check_pkgin()
    ret = {}

    out = __salt__['cmd.run'](
        [pkgin, 'ls'] if pkgin else ['pkg_info'],
        output_loglevel='trace')

    for line in out.splitlines():
        try:
            # Some versions of pkgin check isatty unfortunately
            # this results in cases where a ' ' or ';' can be used
            pkg, ver = re.split('[; ]', line, 1)[0].rsplit('-', 1)
        except ValueError:
            continue
        __salt__['pkg_resource.add_pkg'](ret, pkg, ver)

    __salt__['pkg_resource.sort_pkglist'](ret)
    __context__['pkg.list_pkgs'] = copy.deepcopy(ret)
    if not versions_as_list:
        __salt__['pkg_resource.stringify'](ret)
    return ret


def list_upgrades(refresh=True, **kwargs):
    '''
    List all available package upgrades.

    .. versionadded:: 2018.3.0

    refresh
        Whether or not to refresh the package database before installing.

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.list_upgrades
    '''
    pkgs = {}
    for pkg in sorted(list_pkgs(refresh=refresh).keys()):
        # NOTE: we already optionally refreshed in de list_pkg call
        pkg_upgrade = latest_version(pkg, refresh=False)
        if pkg_upgrade:
            pkgs[pkg] = pkg_upgrade
    return pkgs


def install(name=None, refresh=False, fromrepo=None,
            pkgs=None, sources=None, **kwargs):
    '''
    Install the passed package

    name
        The name of the package to be installed.

    refresh
        Whether or not to refresh the package database before installing.

    fromrepo
        Specify a package repository to install from.


    Multiple Package Installation Options:

    pkgs
        A list of packages to install from a software repository. Must be
        passed as a python list.

        CLI Example:

        .. code-block:: bash

            salt '*' pkg.install pkgs='["foo","bar"]'

    sources
        A list of packages to install. Must be passed as a list of dicts,
        with the keys being package names, and the values being the source URI
        or local path to the package.

        CLI Example:

        .. code-block:: bash

            salt '*' pkg.install sources='[{"foo": "salt://foo.deb"},{"bar": "salt://bar.deb"}]'

    Return a dict containing the new package names and versions::

        {'<package>': {'old': '<old-version>',
                       'new': '<new-version>'}}

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.install <package name>
    '''
    try:
        pkg_params, pkg_type = __salt__['pkg_resource.parse_targets'](
            name, pkgs, sources, **kwargs
        )
    except MinionError as exc:
        raise CommandExecutionError(exc)

    # Support old "repo" argument
    repo = kwargs.get('repo', '')
    if not fromrepo and repo:
        fromrepo = repo

    if not pkg_params:
        return {}

    env = []
    args = []
    pkgin = _check_pkgin()
    if pkgin:
        cmd = pkgin
        if fromrepo:
            log.info('Setting PKG_REPOS=%s', fromrepo)
            env.append(('PKG_REPOS', fromrepo))
    else:
        cmd = 'pkg_add'
        if fromrepo:
            log.info('Setting PKG_PATH=%s', fromrepo)
            env.append(('PKG_PATH', fromrepo))

    if pkg_type == 'file':
        cmd = 'pkg_add'
    elif pkg_type == 'repository':
        if pkgin:
            if refresh:
                args.append('-f')  # update repo db
            args.extend(('-y', 'in'))  # Assume yes when asked

    args.insert(0, cmd)
    args.extend(pkg_params)

    old = list_pkgs()

    out = __salt__['cmd.run_all'](args, env=env, output_loglevel='trace')

    if out['retcode'] != 0 and out['stderr']:
        errors = [out['stderr']]
    else:
        errors = []

    __context__.pop('pkg.list_pkgs', None)
    new = list_pkgs()
    ret = salt.utils.data.compare_dicts(old, new)

    if errors:
        raise CommandExecutionError(
            'Problem encountered installing package(s)',
            info={'errors': errors, 'changes': ret}
        )

    _rehash()
    return ret


def upgrade(refresh=True, pkgs=None, **kwargs):
    '''
    Run pkg upgrade, if pkgin used. Otherwise do nothing

    refresh
        Whether or not to refresh the package database before installing.

    Multiple Package Upgrade Options:

    pkgs
        A list of packages to upgrade from a software repository. Must be
        passed as a python list.

        CLI Example:

        .. code-block:: bash

            salt '*' pkg.upgrade pkgs='["foo","bar"]'

    Returns a dictionary containing the changes:

    .. code-block:: python

        {'<package>':  {'old': '<old-version>',
                        'new': '<new-version>'}}


    CLI Example:

    .. code-block:: bash

        salt '*' pkg.upgrade
    '''
    pkgin = _check_pkgin()
    if not pkgin:
        # There is not easy way to upgrade packages with old package system
        return {}

    if salt.utils.data.is_true(refresh):
        refresh_db()

    old = list_pkgs()

    cmds = []
    if not pkgs:
        cmds.append([pkgin, '-y', 'full-upgrade'])
    elif salt.utils.data.is_list(pkgs):
        for pkg in pkgs:
            cmds.append([pkgin, '-y', 'install', pkg])
    else:
        result = {'retcode': 1, 'reason': 'Ignoring the parameter `pkgs` because it is not a list!'}
        log.error(result['reason'])

    for cmd in cmds:
        result = __salt__['cmd.run_all'](cmd,
                                         output_loglevel='trace',
                                         python_shell=False)
        if result['retcode'] != 0:
            break

    __context__.pop('pkg.list_pkgs', None)
    new = list_pkgs()
    ret = salt.utils.data.compare_dicts(old, new)

    if result['retcode'] != 0:
        raise CommandExecutionError(
            'Problem encountered upgrading packages',
            info={'changes': ret, 'result': result}
        )

    return ret


def remove(name=None, pkgs=None, **kwargs):
    '''
    name
        The name of the package to be deleted.


    Multiple Package Options:

    pkgs
        A list of packages to delete. Must be passed as a python list. The
        ``name`` parameter will be ignored if this option is passed.

    .. versionadded:: 0.16.0


    Returns a list containing the removed packages.

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.remove <package name>
        salt '*' pkg.remove <package1>,<package2>,<package3>
        salt '*' pkg.remove pkgs='["foo", "bar"]'
    '''
    try:
        pkg_params, pkg_type = __salt__['pkg_resource.parse_targets'](
            name, pkgs
        )
    except MinionError as exc:
        raise CommandExecutionError(exc)

    if not pkg_params:
        return {}

    old = list_pkgs()
    args = []

    for param in pkg_params:
        ver = old.get(param, [])
        if not ver:
            continue
        if isinstance(ver, list):
            args.extend(['{0}-{1}'.format(param, v) for v in ver])
        else:
            args.append('{0}-{1}'.format(param, ver))

    if not args:
        return {}

    pkgin = _check_pkgin()
    cmd = [pkgin, '-y', 'remove'] if pkgin else ['pkg_remove']
    cmd.extend(args)

    out = __salt__['cmd.run_all'](cmd, output_loglevel='trace')

    if out['retcode'] != 0 and out['stderr']:
        errors = [out['stderr']]
    else:
        errors = []

    __context__.pop('pkg.list_pkgs', None)
    new = list_pkgs()
    ret = salt.utils.data.compare_dicts(old, new)

    if errors:
        raise CommandExecutionError(
            'Problem encountered removing package(s)',
            info={'errors': errors, 'changes': ret}
        )

    return ret


def purge(name=None, pkgs=None, **kwargs):
    '''
    Package purges are not supported, this function is identical to
    ``remove()``.

    name
        The name of the package to be deleted.


    Multiple Package Options:

    pkgs
        A list of packages to delete. Must be passed as a python list. The
        ``name`` parameter will be ignored if this option is passed.

    .. versionadded:: 0.16.0


    Returns a dict containing the changes.

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.purge <package name>
        salt '*' pkg.purge <package1>,<package2>,<package3>
        salt '*' pkg.purge pkgs='["foo", "bar"]'
    '''
    return remove(name=name, pkgs=pkgs)


def _rehash():
    '''
    Recomputes internal hash table for the PATH variable.
    Use whenever a new command is created during the current
    session.
    '''
    shell = __salt__['environ.get']('SHELL')
    if shell.split('/')[-1] in ('csh', 'tcsh'):
        __salt__['cmd.run']('rehash', output_loglevel='trace')


def file_list(package):
    '''
    List the files that belong to a package.

    CLI Examples:

    .. code-block:: bash

        salt '*' pkg.file_list nginx
    '''
    ret = file_dict(package)
    files = []
    for pkg_files in six.itervalues(ret['files']):
        files.extend(pkg_files)
    ret['files'] = files
    return ret


def file_dict(*packages):
    '''
    .. versionchanged: 2016.3.0

    List the files that belong to a package.

    CLI Examples:

    .. code-block:: bash

        salt '*' pkg.file_dict nginx
        salt '*' pkg.file_dict nginx varnish
    '''
    errors = []
    files = {}

    for package in packages:
        cmd = ['pkg_info', '-qL', package]
        ret = __salt__['cmd.run_all'](cmd, output_loglevel='trace')
        files[package] = []
        for line in ret['stderr'].splitlines():
            errors.append(line)

        for line in ret['stdout'].splitlines():
            if line.startswith('/'):
                files[package].append(line)
            else:
                continue  # unexpected string

    ret = {'errors': errors, 'files': files}
    for field in list(ret):
        if not ret[field] or ret[field] == '':
            del ret[field]
    return ret

# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4