<reponame>Noah-Huppert/salt<filename>salt/modules/pkgutil.py
# -*- coding: utf-8 -*-
"""
Pkgutil support for Solaris

.. 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>`.
"""
from __future__ import absolute_import, print_function, unicode_literals

# Import python libs
import copy

# Import salt libs
import salt.utils.data
import salt.utils.functools
import salt.utils.pkg
import salt.utils.versions
from salt.exceptions import CommandExecutionError, MinionError
from salt.ext import six

# Define the module's virtual name
__virtualname__ = "pkgutil"


def __virtual__():
    """
    Set the virtual pkg module if the os is Solaris
    """
    if __grains__["os_family"] == "Solaris":
        return __virtualname__
    return (
        False,
        "The pkgutil execution module cannot be loaded: "
        "only available on Solaris systems.",
    )


def refresh_db():
    """
    Updates the pkgutil repo database (pkgutil -U)

    CLI Example:

    .. code-block:: bash

        salt '*' pkgutil.refresh_db
    """
    # Remove rtag file to keep multiple refreshes from happening in pkg states
    salt.utils.pkg.clear_rtag(__opts__)
    return __salt__["cmd.retcode"]("/opt/csw/bin/pkgutil -U") == 0


def upgrade_available(name):
    """
    Check if there is an upgrade available for a certain package

    CLI Example:

    .. code-block:: bash

        salt '*' pkgutil.upgrade_available CSWpython
    """
    version_num = None
    cmd = "/opt/csw/bin/pkgutil -c --parse --single {0}".format(name)
    out = __salt__["cmd.run_stdout"](cmd)
    if out:
        version_num = out.split()[2].strip()
    if version_num:
        if version_num == "SAME":
            return ""
        else:
            return version_num
    return ""


def list_upgrades(refresh=True, **kwargs):  # pylint: disable=W0613
    """
    List all available package upgrades on this system

    CLI Example:

    .. code-block:: bash

        salt '*' pkgutil.list_upgrades
    """
    if salt.utils.data.is_true(refresh):
        refresh_db()
    upgrades = {}
    lines = __salt__["cmd.run_stdout"]("/opt/csw/bin/pkgutil -A --parse").splitlines()
    for line in lines:
        comps = line.split("\t")
        if comps[2] == "SAME":
            continue
        if comps[2] == "not installed":
            continue
        upgrades[comps[0]] = comps[1]
    return upgrades


def upgrade(refresh=True):
    """
    Upgrade all of the packages to the latest available version.

    Returns a dict containing the changes::

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

    CLI Example:

    .. code-block:: bash

        salt '*' pkgutil.upgrade
    """
    if salt.utils.data.is_true(refresh):
        refresh_db()

    old = list_pkgs()

    # Install or upgrade the package
    # If package is already installed
    cmd = "/opt/csw/bin/pkgutil -yu"
    __salt__["cmd.run_all"](cmd)
    __context__.pop("pkg.list_pkgs", None)
    new = list_pkgs()
    return salt.utils.data.compare_dicts(old, new)


def list_pkgs(versions_as_list=False, **kwargs):
    """
    List the packages currently installed as a dict::

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

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.list_pkgs
        salt '*' pkg.list_pkgs versions_as_list=True
    """
    versions_as_list = salt.utils.data.is_true(versions_as_list)
    # 'removed' not yet implemented or not applicable
    if salt.utils.data.is_true(kwargs.get("removed")):
        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

    ret = {}
    cmd = "/usr/bin/pkginfo -x"

    # Package information returned two lines per package. On even-offset
    # lines, the package name is in the first column. On odd-offset lines, the
    # package version is in the second column.
    lines = __salt__["cmd.run"](cmd).splitlines()
    for index, line in enumerate(lines):
        if index % 2 == 0:
            name = line.split()[0].strip()
        if index % 2 == 1:
            version_num = line.split()[1].strip()
            __salt__["pkg_resource.add_pkg"](ret, name, version_num)

    __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 version(*names, **kwargs):
    """
    Returns a version if the package is installed, else returns an empty string

    CLI Example:

    .. code-block:: bash

        salt '*' pkgutil.version CSWpython
    """
    return __salt__["pkg_resource.version"](*names, **kwargs)


def latest_version(*names, **kwargs):
    """
    Return the latest version of the named package available for upgrade or
    installation. If more than one package name is specified, a dict of
    name/version pairs is returned.

    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 '*' pkgutil.latest_version CSWpython
        salt '*' pkgutil.latest_version <package1> <package2> <package3> ...
    """
    refresh = salt.utils.data.is_true(kwargs.pop("refresh", True))

    if not names:
        return ""
    ret = {}
    # Initialize the dict with empty strings
    for name in names:
        ret[name] = ""

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

    pkgs = list_pkgs()
    cmd = "/opt/csw/bin/pkgutil -a --parse {0}".format(" ".join(names))
    output = __salt__["cmd.run_all"](cmd).get("stdout", "").splitlines()
    for line in output:
        try:
            name, version_rev = line.split()[1:3]
        except ValueError:
            continue

        if name in names:
            cver = pkgs.get(name, "")
            nver = version_rev.split(",")[0]
            if not cver or salt.utils.versions.compare(ver1=cver, oper="<", ver2=nver):
                # Remove revision for version comparison
                ret[name] = version_rev

    # Return a string if only one package name passed
    if len(names) == 1:
        return ret[names[0]]
    return ret


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


def install(name=None, refresh=False, version=None, pkgs=None, **kwargs):
    """
    Install packages using the pkgutil tool.

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.install <package_name>
        salt '*' pkg.install SMClgcc346


    Multiple Package Installation Options:

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

        CLI Example:

        .. code-block:: bash

            salt '*' pkg.install pkgs='["foo", "bar"]'
            salt '*' pkg.install pkgs='["foo", {"bar": "1.2.3"}]'


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

        {'<package>': {'old': '<old-version>',
                       'new': '<new-version>'}}
    """
    if refresh:
        refresh_db()

    try:
        # Ignore 'sources' argument
        pkg_params = __salt__["pkg_resource.parse_targets"](name, pkgs, **kwargs)[0]
    except MinionError as exc:
        raise CommandExecutionError(exc)

    if pkg_params is None or len(pkg_params) == 0:
        return {}

    if pkgs is None and version and len(pkg_params) == 1:
        pkg_params = {name: version}
    targets = []
    for param, pkgver in six.iteritems(pkg_params):
        if pkgver is None:
            targets.append(param)
        else:
            targets.append("{0}-{1}".format(param, pkgver))

    cmd = "/opt/csw/bin/pkgutil -yu {0}".format(" ".join(targets))
    old = list_pkgs()
    __salt__["cmd.run_all"](cmd)
    __context__.pop("pkg.list_pkgs", None)
    new = list_pkgs()
    return salt.utils.data.compare_dicts(old, new)


def remove(name=None, pkgs=None, **kwargs):
    """
    Remove a package and all its dependencies which are not in use by other
    packages.

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

    old = list_pkgs()
    targets = [x for x in pkg_params if x in old]
    if not targets:
        return {}
    cmd = "/opt/csw/bin/pkgutil -yr {0}".format(" ".join(targets))
    __salt__["cmd.run_all"](cmd)
    __context__.pop("pkg.list_pkgs", None)
    new = list_pkgs()
    return salt.utils.data.compare_dicts(old, new)


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)

# -*- coding: utf-8 -*-
"""
Pkgutil support for Solaris

.. 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>`.
"""
from __future__ import absolute_import, print_function, unicode_literals

# Import python libs
import copy

# Import salt libs
import salt.utils.data
import salt.utils.functools
import salt.utils.pkg
import salt.utils.versions
from salt.exceptions import CommandExecutionError, MinionError
from salt.ext import six

# Define the module's virtual name
__virtualname__ = "pkgutil"


def __virtual__():
    """
    Set the virtual pkg module if the os is Solaris
    """
    if __grains__["os_family"] == "Solaris":
        return __virtualname__
    return (
        False,
        "The pkgutil execution module cannot be loaded: "
        "only available on Solaris systems.",
    )


def refresh_db():
    """
    Updates the pkgutil repo database (pkgutil -U)

    CLI Example:

    .. code-block:: bash

        salt '*' pkgutil.refresh_db
    """
    # Remove rtag file to keep multiple refreshes from happening in pkg states
    salt.utils.pkg.clear_rtag(__opts__)
    return __salt__["cmd.retcode"]("/opt/csw/bin/pkgutil -U") == 0


def upgrade_available(name):
    """
    Check if there is an upgrade available for a certain package

    CLI Example:

    .. code-block:: bash

        salt '*' pkgutil.upgrade_available CSWpython
    """
    version_num = None
    cmd = "/opt/csw/bin/pkgutil -c --parse --single {0}".format(name)
    out = __salt__["cmd.run_stdout"](cmd)
    if out:
        version_num = out.split()[2].strip()
    if version_num:
        if version_num == "SAME":
            return ""
        else:
            return version_num
    return ""


def list_upgrades(refresh=True, **kwargs):  # pylint: disable=W0613
    """
    List all available package upgrades on this system

    CLI Example:

    .. code-block:: bash

        salt '*' pkgutil.list_upgrades
    """
    if salt.utils.data.is_true(refresh):
        refresh_db()
    upgrades = {}
    lines = __salt__["cmd.run_stdout"]("/opt/csw/bin/pkgutil -A --parse").splitlines()
    for line in lines:
        comps = line.split("\t")
        if comps[2] == "SAME":
            continue
        if comps[2] == "not installed":
            continue
        upgrades[comps[0]] = comps[1]
    return upgrades


def upgrade(refresh=True):
    """
    Upgrade all of the packages to the latest available version.

    Returns a dict containing the changes::

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

    CLI Example:

    .. code-block:: bash

        salt '*' pkgutil.upgrade
    """
    if salt.utils.data.is_true(refresh):
        refresh_db()

    old = list_pkgs()

    # Install or upgrade the package
    # If package is already installed
    cmd = "/opt/csw/bin/pkgutil -yu"
    __salt__["cmd.run_all"](cmd)
    __context__.pop("pkg.list_pkgs", None)
    new = list_pkgs()
    return salt.utils.data.compare_dicts(old, new)


def list_pkgs(versions_as_list=False, **kwargs):
    """
    List the packages currently installed as a dict::

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

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.list_pkgs
        salt '*' pkg.list_pkgs versions_as_list=True
    """
    versions_as_list = salt.utils.data.is_true(versions_as_list)
    # 'removed' not yet implemented or not applicable
    if salt.utils.data.is_true(kwargs.get("removed")):
        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

    ret = {}
    cmd = "/usr/bin/pkginfo -x"

    # Package information returned two lines per package. On even-offset
    # lines, the package name is in the first column. On odd-offset lines, the
    # package version is in the second column.
    lines = __salt__["cmd.run"](cmd).splitlines()
    for index, line in enumerate(lines):
        if index % 2 == 0:
            name = line.split()[0].strip()
        if index % 2 == 1:
            version_num = line.split()[1].strip()
            __salt__["pkg_resource.add_pkg"](ret, name, version_num)

    __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 version(*names, **kwargs):
    """
    Returns a version if the package is installed, else returns an empty string

    CLI Example:

    .. code-block:: bash

        salt '*' pkgutil.version CSWpython
    """
    return __salt__["pkg_resource.version"](*names, **kwargs)


def latest_version(*names, **kwargs):
    """
    Return the latest version of the named package available for upgrade or
    installation. If more than one package name is specified, a dict of
    name/version pairs is returned.

    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 '*' pkgutil.latest_version CSWpython
        salt '*' pkgutil.latest_version <package1> <package2> <package3> ...
    """
    refresh = salt.utils.data.is_true(kwargs.pop("refresh", True))

    if not names:
        return ""
    ret = {}
    # Initialize the dict with empty strings
    for name in names:
        ret[name] = ""

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

    pkgs = list_pkgs()
    cmd = "/opt/csw/bin/pkgutil -a --parse {0}".format(" ".join(names))
    output = __salt__["cmd.run_all"](cmd).get("stdout", "").splitlines()
    for line in output:
        try:
            name, version_rev = line.split()[1:3]
        except ValueError:
            continue

        if name in names:
            cver = pkgs.get(name, "")
            nver = version_rev.split(",")[0]
            if not cver or salt.utils.versions.compare(ver1=cver, oper="<", ver2=nver):
                # Remove revision for version comparison
                ret[name] = version_rev

    # Return a string if only one package name passed
    if len(names) == 1:
        return ret[names[0]]
    return ret


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


def install(name=None, refresh=False, version=None, pkgs=None, **kwargs):
    """
    Install packages using the pkgutil tool.

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.install <package_name>
        salt '*' pkg.install SMClgcc346


    Multiple Package Installation Options:

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

        CLI Example:

        .. code-block:: bash

            salt '*' pkg.install pkgs='["foo", "bar"]'
            salt '*' pkg.install pkgs='["foo", {"bar": "1.2.3"}]'


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

        {'<package>': {'old': '<old-version>',
                       'new': '<new-version>'}}
    """
    if refresh:
        refresh_db()

    try:
        # Ignore 'sources' argument
        pkg_params = __salt__["pkg_resource.parse_targets"](name, pkgs, **kwargs)[0]
    except MinionError as exc:
        raise CommandExecutionError(exc)

    if pkg_params is None or len(pkg_params) == 0:
        return {}

    if pkgs is None and version and len(pkg_params) == 1:
        pkg_params = {name: version}
    targets = []
    for param, pkgver in six.iteritems(pkg_params):
        if pkgver is None:
            targets.append(param)
        else:
            targets.append("{0}-{1}".format(param, pkgver))

    cmd = "/opt/csw/bin/pkgutil -yu {0}".format(" ".join(targets))
    old = list_pkgs()
    __salt__["cmd.run_all"](cmd)
    __context__.pop("pkg.list_pkgs", None)
    new = list_pkgs()
    return salt.utils.data.compare_dicts(old, new)


def remove(name=None, pkgs=None, **kwargs):
    """
    Remove a package and all its dependencies which are not in use by other
    packages.

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

    old = list_pkgs()
    targets = [x for x in pkg_params if x in old]
    if not targets:
        return {}
    cmd = "/opt/csw/bin/pkgutil -yr {0}".format(" ".join(targets))
    __salt__["cmd.run_all"](cmd)
    __context__.pop("pkg.list_pkgs", None)
    new = list_pkgs()
    return salt.utils.data.compare_dicts(old, new)


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)
# -*- coding: utf-8 -*- Pkgutil support for Solaris  .. 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 # Import salt libs # Define the module's virtual name Set the virtual pkg module if the os is Solaris Updates the pkgutil repo database (pkgutil -U)      CLI Example:      .. code-block:: bash          salt '*' pkgutil.refresh_db # Remove rtag file to keep multiple refreshes from happening in pkg states Check if there is an upgrade available for a certain package      CLI Example:      .. code-block:: bash          salt '*' pkgutil.upgrade_available CSWpython # pylint: disable=W0613 List all available package upgrades on this system      CLI Example:      .. code-block:: bash          salt '*' pkgutil.list_upgrades Upgrade all of the packages to the latest available version.      Returns a dict containing the changes::          {'<package>': {'old': '<old-version>',                        'new': '<new-version>'}}      CLI Example:      .. code-block:: bash          salt '*' pkgutil.upgrade # Install or upgrade the package # If package is already installed List the packages currently installed as a dict::          {'<package_name>': '<version>'}      CLI Example:      .. code-block:: bash          salt '*' pkg.list_pkgs         salt '*' pkg.list_pkgs versions_as_list=True # 'removed' not yet implemented or not applicable # Package information returned two lines per package. On even-offset # lines, the package name is in the first column. On odd-offset lines, the # package version is in the second column. Returns a version if the package is installed, else returns an empty string      CLI Example:      .. code-block:: bash          salt '*' pkgutil.version CSWpython Return the latest version of the named package available for upgrade or     installation. If more than one package name is specified, a dict of     name/version pairs is returned.      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 '*' pkgutil.latest_version CSWpython         salt '*' pkgutil.latest_version <package1> <package2> <package3> ... # Initialize the dict with empty strings # Refresh before looking for the latest version available # Remove revision for version comparison # Return a string if only one package name passed # available_version is being deprecated Install packages using the pkgutil tool.      CLI Example:      .. code-block:: bash          salt '*' pkg.install <package_name>         salt '*' pkg.install SMClgcc346       Multiple Package Installation Options:      pkgs         A list of packages to install from OpenCSW. Must be passed as a python         list.          CLI Example:          .. code-block:: bash              salt '*' pkg.install pkgs='["foo", "bar"]'             salt '*' pkg.install pkgs='["foo", {"bar": "1.2.3"}]'       Returns a dict containing the new package names and versions::          {'<package>': {'old': '<old-version>',                        'new': '<new-version>'}} # Ignore 'sources' argument Remove a package and all its dependencies which are not in use by other     packages.      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.remove <package name>         salt '*' pkg.remove <package1>,<package2>,<package3>         salt '*' pkg.remove pkgs='["foo", "bar"]' 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"]'