<commit_before># -*- coding: utf-8 -*-
"""
Utility functions for use with or in SLS files
"""

# Import Python libs
from __future__ import absolute_import, print_function, unicode_literals

import os
import textwrap

# Import Salt libs
import salt.exceptions
import salt.loader
import salt.template
import salt.utils.args
import salt.utils.dictupdate

def update(dest, upd, recursive_update=True, merge_lists=False):
    """
    Merge ``upd`` recursively into ``dest``

    If ``merge_lists=True``, will aggregate list object types instead of
    replacing. This behavior is only activated when ``recursive_update=True``.

    CLI Example:

    .. code-block:: shell

        salt '*' slsutil.update '{foo: Foo}' '{bar: Bar}'

    """
    return salt.utils.dictupdate.update(dest, upd, recursive_update, merge_lists)

def merge(obj_a, obj_b, strategy="smart", renderer="yaml", merge_lists=False):
    """
    Merge a data structure into another by choosing a merge strategy

    Strategies:

    * aggregate
    * list
    * overwrite
    * recurse
    * smart

    CLI Example:

    .. code-block:: shell

        salt '*' slsutil.merge '{foo: Foo}' '{bar: Bar}'
    """
    return salt.utils.dictupdate.merge(obj_a, obj_b, strategy, renderer, merge_lists)

def merge_all(lst, strategy="smart", renderer="yaml", merge_lists=False):
    """
    .. versionadded:: 2019.2.0

    Merge a list of objects into each other in order

    :type lst: Iterable
    :param lst: List of objects to be merged.

    :type strategy: String
    :param strategy: Merge strategy. See utils.dictupdate.

    :type renderer: String
    :param renderer:
        Renderer type. Used to determine strategy when strategy is 'smart'.

    :type merge_lists: Bool
    :param merge_lists: Defines whether to merge embedded object lists.

    CLI Example:

    .. code-block:: shell

        $ salt-call --output=txt slsutil.merge_all '[{foo: Foo}, {foo: Bar}]'
        local: {u'foo': u'Bar'}
    """

    ret = {}
    for obj in lst:
        ret = salt.utils.dictupdate.merge(ret, obj, strategy, renderer, merge_lists)

    return ret

def renderer(path=None, string=None, default_renderer="jinja|yaml", **kwargs):
    """
    Parse a string or file through Salt's renderer system

    .. versionchanged:: 2018.3.0
       Add support for Salt fileserver URIs.

    This is an open-ended function and can be used for a variety of tasks. It
    makes use of Salt's "renderer pipes" system to run a string or file through
    a pipe of any of the loaded renderer modules.

    :param path: The path to a file on Salt's fileserver (any URIs supported by
        :py:func:`cp.get_url <salt.modules.cp.get_url>`) or on the local file
        system.
    :param string: An inline string to be used as the file to send through the
        renderer system. Note, not all renderer modules can work with strings;
        the 'py' renderer requires a file, for example.
    :param default_renderer: The renderer pipe to send the file through; this
        is overridden by a "she-bang" at the top of the file.
    :param kwargs: Keyword args to pass to Salt's compile_template() function.

    Keep in mind the goal of each renderer when choosing a render-pipe; for
    example, the Jinja renderer processes a text file and produces a string,
    however the YAML renderer processes a text file and produces a data
    structure.

    One possible use is to allow writing "map files", as are commonly seen in
    Salt formulas, but without tying the renderer of the map file to the
    renderer used in the other sls files. In other words, a map file could use
    the Python renderer and still be included and used by an sls file that uses
    the default 'jinja|yaml' renderer.

    For example, the two following map files produce identical results but one
    is written using the normal 'jinja|yaml' and the other is using 'py':

    .. code-block:: jinja

        #!jinja|yaml
        {% set apache = salt.grains.filter_by({
            ...normal jinja map file here...
        }, merge=salt.pillar.get('apache:lookup')) %}
        {{ apache | yaml() }}

    .. code-block:: python

        #!py
        def run():
            apache = __salt__.grains.filter_by({
                ...normal map here but as a python dict...
            }, merge=__salt__.pillar.get('apache:lookup'))
            return apache

    Regardless of which of the above map files is used, it can be accessed from
    any other sls file by calling this function. The following is a usage
    example in Jinja:

    .. code-block:: jinja

        {% set apache = salt.slsutil.renderer('map.sls') %}

    CLI Example:

    .. code-block:: bash

        salt '*' slsutil.renderer salt://path/to/file
        salt '*' slsutil.renderer /path/to/file
        salt '*' slsutil.renderer /path/to/file.jinja 'jinja'
        salt '*' slsutil.renderer /path/to/file.sls 'jinja|yaml'
        salt '*' slsutil.renderer string='Inline template! {{ saltenv }}'
        salt '*' slsutil.renderer string='Hello, {{ name }}.' name='world'
    """
    if not path and not string:
        raise salt.exceptions.SaltInvocationError("Must pass either path or string")

    renderers = salt.loader.render(__opts__, __salt__)

    if path:
        path_or_string = __salt__["cp.get_url"](
            path, saltenv=kwargs.get("saltenv", "base")
        )
    elif string:
        path_or_string = ":string:"
        kwargs["input_data"] = string

    ret = salt.template.compile_template(
        path_or_string,
        renderers,
        default_renderer,
        __opts__["renderer_blacklist"],
        __opts__["renderer_whitelist"],
        **kwargs
    )
    return ret.read() if __utils__["stringio.is_readable"](ret) else ret

def _get_serialize_fn(serializer, fn_name):
    serializers = salt.loader.serializers(__opts__)
    fns = getattr(serializers, serializer, None)
    fn = getattr(fns, fn_name, None)

    if not fns:
        raise salt.exceptions.CommandExecutionError(
            "Serializer '{0}' not found.".format(serializer)
        )

    if not fn:
        raise salt.exceptions.CommandExecutionError(
            "Serializer '{0}' does not implement {1}.".format(serializer, fn_name)
        )

    return fn

def serialize(serializer, obj, **mod_kwargs):
    """
    Serialize a Python object using one of the available
    :ref:`all-salt.serializers`.

    CLI Example:

    .. code-block:: bash

        salt '*' --no-parse=obj slsutil.serialize 'json' obj="{'foo': 'Foo!'}

    Jinja Example:

    .. code-block:: jinja

        {% set json_string = salt.slsutil.serialize('json',
            {'foo': 'Foo!'}) %}
    """
    kwargs = salt.utils.args.clean_kwargs(**mod_kwargs)
    return _get_serialize_fn(serializer, "serialize")(obj, **kwargs)

def deserialize(serializer, stream_or_string, **mod_kwargs):
    """
    Deserialize a Python object using one of the available
    :ref:`all-salt.serializers`.

    CLI Example:

    .. code-block:: bash

        salt '*' slsutil.deserialize 'json' '{"foo": "Foo!"}'
        salt '*' --no-parse=stream_or_string slsutil.deserialize 'json' \\
            stream_or_string='{"foo": "Foo!"}'

    Jinja Example:

    .. code-block:: jinja

        {% set python_object = salt.slsutil.deserialize('json',
            '{"foo": "Foo!"}') %}
    """
    kwargs = salt.utils.args.clean_kwargs(**mod_kwargs)
    return _get_serialize_fn(serializer, "deserialize")(stream_or_string, **kwargs)

def banner(
    width=72,
    commentchar="#",
    borderchar="#",
    blockstart=None,
    blockend=None,
    title=None,
    text=None,
    newline=False,
):
    """
    Create a standardized comment block to include in a templated file.

    A common technique in configuration management is to include a comment
    block in managed files, warning users not to modify the file. This
    function simplifies and standardizes those comment blocks.

    :param width: The width, in characters, of the banner. Default is 72.
    :param commentchar: The character to be used in the starting position of
        each line. This value should be set to a valid line comment character
        for the syntax of the file in which the banner is being inserted.
        Multiple character sequences, like '//' are supported.
        If the file's syntax does not support line comments (such as XML),
        use the ``blockstart`` and ``blockend`` options.
    :param borderchar: The character to use in the top and bottom border of
        the comment box. Must be a single character.
    :param blockstart: The character sequence to use at the beginning of a
        block comment. Should be used in conjunction with ``blockend``
    :param blockend: The character sequence to use at the end of a
        block comment. Should be used in conjunction with ``blockstart``
    :param title: The first field of the comment block. This field appears
        centered at the top of the box.
    :param text: The second filed of the comment block. This field appears
        left-justifed at the bottom of the box.
    :param newline: Boolean value to indicate whether the comment block should
        end with a newline. Default is ``False``.

    **Example 1 - the default banner:**

    .. code-block:: jinja

        {{ salt['slsutil.banner']() }}

    .. code-block:: none

        ########################################################################
        #                                                                      #
        #              THIS FILE IS MANAGED BY SALT - DO NOT EDIT              #
        #                                                                      #
        # The contents of this file are managed by Salt. Any changes to this   #
        # file may be overwritten automatically and without warning.           #
        ########################################################################

    **Example 2 - a Javadoc-style banner:**

    .. code-block:: jinja

        {{ salt['slsutil.banner'](commentchar=' *', borderchar='*', blockstart='/**', blockend=' */') }}

    .. code-block:: none

        /**
         ***********************************************************************
         *                                                                     *
         *              THIS FILE IS MANAGED BY SALT - DO NOT EDIT             *
         *                                                                     *
         * The contents of this file are managed by Salt. Any changes to this  *
         * file may be overwritten automatically and without warning.          *
         ***********************************************************************
         */

    **Example 3 - custom text:**

    .. code-block:: jinja

        {{ set copyright='This file may not be copied or distributed without the express permission of ACME Corp.' }}
        {{ salt['slsutil.banner'](title='Copyright 2019 ACME Corp', text=copyright) }}

    .. code-block:: none

        ########################################################################
        #                                                                      #
        #                       Copyright 2019 ACME Corp                       #
        #                                                                      #
        # This file may not be copied or distributed without the express       #
        # permission of ACME Corp.                                             #
        ########################################################################

    """

    if title is None:
        title = "THIS FILE IS MANAGED BY SALT - DO NOT EDIT"

    if text is None:
        text = (
            "The contents of this file are managed by Salt. "
            "Any changes to this file may be overwritten "
            "automatically and without warning."
        )

    # Set up some typesetting variables
    ledge = commentchar.rstrip()
    redge = commentchar.strip()
    lgutter = ledge + " "
    rgutter = " " + redge
    textwidth = width - len(lgutter) - len(rgutter)

    # Define the static elements
    border_line = (
        commentchar + borderchar[:1] * (width - len(ledge) - len(redge)) + redge
    )
    spacer_line = commentchar + " " * (width - len(commentchar) * 2) + commentchar

    # Create the banner
    wrapper = textwrap.TextWrapper(width=(width - len(lgutter) - len(rgutter)))
    block = list()
    if blockstart is not None:
        block.append(blockstart)
    block.append(border_line)
    block.append(spacer_line)
    for line in wrapper.wrap(title):
        block.append(lgutter + line.center(textwidth) + rgutter)
    block.append(spacer_line)
    for line in wrapper.wrap(text):
        block.append(lgutter + line + " " * (textwidth - len(line)) + rgutter)
    block.append(border_line)
    if blockend is not None:
        block.append(blockend)

    # Convert list to multi-line string
    result = os.linesep.join(block)

    # Add a newline character to the end of the banner
    if newline:
        return result + os.linesep

    return result

def boolstr(value, true="true", false="false"):
    """
    Convert a boolean value into a string. This function is
    intended to be used from within file templates to provide
    an easy way to take boolean values stored in Pillars or
    Grains, and write them out in the apprpriate syntax for
    a particular file template.

    :param value: The boolean value to be converted
    :param true: The value to return if ``value`` is ``True``
    :param false: The value to return if ``value`` is ``False``

    In this example, a pillar named ``smtp:encrypted`` stores a boolean
    value, but the template that uses that value needs ``yes`` or ``no``
    to be written, based on the boolean value.

    *Note: this is written on two lines for clarity. The same result
    could be achieved in one line.*

    .. code-block:: jinja

        {% set encrypted = salt[pillar.get]('smtp:encrypted', false) %}
        use_tls: {{ salt['slsutil.boolstr'](encrypted, 'yes', 'no') }}

    Result (assuming the value is ``True``):

    .. code-block:: none

        use_tls: yes

    """

    if value:
        return true

    return false
<commit_msg>Handle exception in slsutil.banner where width is too small to render<commit_after># -*- coding: utf-8 -*-
"""
Utility functions for use with or in SLS files
"""

# Import Python libs
from __future__ import absolute_import, print_function, unicode_literals

import os
import textwrap

# Import Salt libs
import salt.exceptions
import salt.loader
import salt.template
import salt.utils.args
import salt.utils.dictupdate

def update(dest, upd, recursive_update=True, merge_lists=False):
    """
    Merge ``upd`` recursively into ``dest``

    If ``merge_lists=True``, will aggregate list object types instead of
    replacing. This behavior is only activated when ``recursive_update=True``.

    CLI Example:

    .. code-block:: shell

        salt '*' slsutil.update '{foo: Foo}' '{bar: Bar}'

    """
    return salt.utils.dictupdate.update(dest, upd, recursive_update, merge_lists)

def merge(obj_a, obj_b, strategy="smart", renderer="yaml", merge_lists=False):
    """
    Merge a data structure into another by choosing a merge strategy

    Strategies:

    * aggregate
    * list
    * overwrite
    * recurse
    * smart

    CLI Example:

    .. code-block:: shell

        salt '*' slsutil.merge '{foo: Foo}' '{bar: Bar}'
    """
    return salt.utils.dictupdate.merge(obj_a, obj_b, strategy, renderer, merge_lists)

def merge_all(lst, strategy="smart", renderer="yaml", merge_lists=False):
    """
    .. versionadded:: 2019.2.0

    Merge a list of objects into each other in order

    :type lst: Iterable
    :param lst: List of objects to be merged.

    :type strategy: String
    :param strategy: Merge strategy. See utils.dictupdate.

    :type renderer: String
    :param renderer:
        Renderer type. Used to determine strategy when strategy is 'smart'.

    :type merge_lists: Bool
    :param merge_lists: Defines whether to merge embedded object lists.

    CLI Example:

    .. code-block:: shell

        $ salt-call --output=txt slsutil.merge_all '[{foo: Foo}, {foo: Bar}]'
        local: {u'foo': u'Bar'}
    """

    ret = {}
    for obj in lst:
        ret = salt.utils.dictupdate.merge(ret, obj, strategy, renderer, merge_lists)

    return ret

def renderer(path=None, string=None, default_renderer="jinja|yaml", **kwargs):
    """
    Parse a string or file through Salt's renderer system

    .. versionchanged:: 2018.3.0
       Add support for Salt fileserver URIs.

    This is an open-ended function and can be used for a variety of tasks. It
    makes use of Salt's "renderer pipes" system to run a string or file through
    a pipe of any of the loaded renderer modules.

    :param path: The path to a file on Salt's fileserver (any URIs supported by
        :py:func:`cp.get_url <salt.modules.cp.get_url>`) or on the local file
        system.
    :param string: An inline string to be used as the file to send through the
        renderer system. Note, not all renderer modules can work with strings;
        the 'py' renderer requires a file, for example.
    :param default_renderer: The renderer pipe to send the file through; this
        is overridden by a "she-bang" at the top of the file.
    :param kwargs: Keyword args to pass to Salt's compile_template() function.

    Keep in mind the goal of each renderer when choosing a render-pipe; for
    example, the Jinja renderer processes a text file and produces a string,
    however the YAML renderer processes a text file and produces a data
    structure.

    One possible use is to allow writing "map files", as are commonly seen in
    Salt formulas, but without tying the renderer of the map file to the
    renderer used in the other sls files. In other words, a map file could use
    the Python renderer and still be included and used by an sls file that uses
    the default 'jinja|yaml' renderer.

    For example, the two following map files produce identical results but one
    is written using the normal 'jinja|yaml' and the other is using 'py':

    .. code-block:: jinja

        #!jinja|yaml
        {% set apache = salt.grains.filter_by({
            ...normal jinja map file here...
        }, merge=salt.pillar.get('apache:lookup')) %}
        {{ apache | yaml() }}

    .. code-block:: python

        #!py
        def run():
            apache = __salt__.grains.filter_by({
                ...normal map here but as a python dict...
            }, merge=__salt__.pillar.get('apache:lookup'))
            return apache

    Regardless of which of the above map files is used, it can be accessed from
    any other sls file by calling this function. The following is a usage
    example in Jinja:

    .. code-block:: jinja

        {% set apache = salt.slsutil.renderer('map.sls') %}

    CLI Example:

    .. code-block:: bash

        salt '*' slsutil.renderer salt://path/to/file
        salt '*' slsutil.renderer /path/to/file
        salt '*' slsutil.renderer /path/to/file.jinja 'jinja'
        salt '*' slsutil.renderer /path/to/file.sls 'jinja|yaml'
        salt '*' slsutil.renderer string='Inline template! {{ saltenv }}'
        salt '*' slsutil.renderer string='Hello, {{ name }}.' name='world'
    """
    if not path and not string:
        raise salt.exceptions.SaltInvocationError("Must pass either path or string")

    renderers = salt.loader.render(__opts__, __salt__)

    if path:
        path_or_string = __salt__["cp.get_url"](
            path, saltenv=kwargs.get("saltenv", "base")
        )
    elif string:
        path_or_string = ":string:"
        kwargs["input_data"] = string

    ret = salt.template.compile_template(
        path_or_string,
        renderers,
        default_renderer,
        __opts__["renderer_blacklist"],
        __opts__["renderer_whitelist"],
        **kwargs
    )
    return ret.read() if __utils__["stringio.is_readable"](ret) else ret

def _get_serialize_fn(serializer, fn_name):
    serializers = salt.loader.serializers(__opts__)
    fns = getattr(serializers, serializer, None)
    fn = getattr(fns, fn_name, None)

    if not fns:
        raise salt.exceptions.CommandExecutionError(
            "Serializer '{0}' not found.".format(serializer)
        )

    if not fn:
        raise salt.exceptions.CommandExecutionError(
            "Serializer '{0}' does not implement {1}.".format(serializer, fn_name)
        )

    return fn

def serialize(serializer, obj, **mod_kwargs):
    """
    Serialize a Python object using one of the available
    :ref:`all-salt.serializers`.

    CLI Example:

    .. code-block:: bash

        salt '*' --no-parse=obj slsutil.serialize 'json' obj="{'foo': 'Foo!'}

    Jinja Example:

    .. code-block:: jinja

        {% set json_string = salt.slsutil.serialize('json',
            {'foo': 'Foo!'}) %}
    """
    kwargs = salt.utils.args.clean_kwargs(**mod_kwargs)
    return _get_serialize_fn(serializer, "serialize")(obj, **kwargs)

def deserialize(serializer, stream_or_string, **mod_kwargs):
    """
    Deserialize a Python object using one of the available
    :ref:`all-salt.serializers`.

    CLI Example:

    .. code-block:: bash

        salt '*' slsutil.deserialize 'json' '{"foo": "Foo!"}'
        salt '*' --no-parse=stream_or_string slsutil.deserialize 'json' \\
            stream_or_string='{"foo": "Foo!"}'

    Jinja Example:

    .. code-block:: jinja

        {% set python_object = salt.slsutil.deserialize('json',
            '{"foo": "Foo!"}') %}
    """
    kwargs = salt.utils.args.clean_kwargs(**mod_kwargs)
    return _get_serialize_fn(serializer, "deserialize")(stream_or_string, **kwargs)

def banner(
    width=72,
    commentchar="#",
    borderchar="#",
    blockstart=None,
    blockend=None,
    title=None,
    text=None,
    newline=False,
):
    """
    Create a standardized comment block to include in a templated file.

    A common technique in configuration management is to include a comment
    block in managed files, warning users not to modify the file. This
    function simplifies and standardizes those comment blocks.

    :param width: The width, in characters, of the banner. Default is 72.
    :param commentchar: The character to be used in the starting position of
        each line. This value should be set to a valid line comment character
        for the syntax of the file in which the banner is being inserted.
        Multiple character sequences, like '//' are supported.
        If the file's syntax does not support line comments (such as XML),
        use the ``blockstart`` and ``blockend`` options.
    :param borderchar: The character to use in the top and bottom border of
        the comment box. Must be a single character.
    :param blockstart: The character sequence to use at the beginning of a
        block comment. Should be used in conjunction with ``blockend``
    :param blockend: The character sequence to use at the end of a
        block comment. Should be used in conjunction with ``blockstart``
    :param title: The first field of the comment block. This field appears
        centered at the top of the box.
    :param text: The second filed of the comment block. This field appears
        left-justifed at the bottom of the box.
    :param newline: Boolean value to indicate whether the comment block should
        end with a newline. Default is ``False``.

    **Example 1 - the default banner:**

    .. code-block:: jinja

        {{ salt['slsutil.banner']() }}

    .. code-block:: none

        ########################################################################
        #                                                                      #
        #              THIS FILE IS MANAGED BY SALT - DO NOT EDIT              #
        #                                                                      #
        # The contents of this file are managed by Salt. Any changes to this   #
        # file may be overwritten automatically and without warning.           #
        ########################################################################

    **Example 2 - a Javadoc-style banner:**

    .. code-block:: jinja

        {{ salt['slsutil.banner'](commentchar=' *', borderchar='*', blockstart='/**', blockend=' */') }}

    .. code-block:: none

        /**
         ***********************************************************************
         *                                                                     *
         *              THIS FILE IS MANAGED BY SALT - DO NOT EDIT             *
         *                                                                     *
         * The contents of this file are managed by Salt. Any changes to this  *
         * file may be overwritten automatically and without warning.          *
         ***********************************************************************
         */

    **Example 3 - custom text:**

    .. code-block:: jinja

        {{ set copyright='This file may not be copied or distributed without the express permission of ACME Corp.' }}
        {{ salt['slsutil.banner'](title='Copyright 2019 ACME Corp', text=copyright) }}

    .. code-block:: none

        ########################################################################
        #                                                                      #
        #                       Copyright 2019 ACME Corp                       #
        #                                                                      #
        # This file may not be copied or distributed without the express       #
        # permission of ACME Corp.                                             #
        ########################################################################

    """

    if title is None:
        title = "THIS FILE IS MANAGED BY SALT - DO NOT EDIT"

    if text is None:
        text = (
            "The contents of this file are managed by Salt. "
            "Any changes to this file may be overwritten "
            "automatically and without warning."
        )

    # Set up some typesetting variables
    ledge = commentchar.rstrip()
    redge = commentchar.strip()
    lgutter = ledge + " "
    rgutter = " " + redge
    textwidth = width - len(lgutter) - len(rgutter)

    # Check the width
    if textwidth <= 0:
        raise salt.exceptions.ArgumentValueError("Width is too small to render banner")

    # Define the static elements
    border_line = (
        commentchar + borderchar[:1] * (width - len(ledge) - len(redge)) + redge
    )
    spacer_line = commentchar + " " * (width - len(commentchar) * 2) + commentchar

    # Create the banner
    wrapper = textwrap.TextWrapper(width=textwidth)
    block = list()
    if blockstart is not None:
        block.append(blockstart)
    block.append(border_line)
    block.append(spacer_line)
    for line in wrapper.wrap(title):
        block.append(lgutter + line.center(textwidth) + rgutter)
    block.append(spacer_line)
    for line in wrapper.wrap(text):
        block.append(lgutter + line + " " * (textwidth - len(line)) + rgutter)
    block.append(border_line)
    if blockend is not None:
        block.append(blockend)

    # Convert list to multi-line string
    result = os.linesep.join(block)

    # Add a newline character to the end of the banner
    if newline:
        return result + os.linesep

    return result

def boolstr(value, true="true", false="false"):
    """
    Convert a boolean value into a string. This function is
    intended to be used from within file templates to provide
    an easy way to take boolean values stored in Pillars or
    Grains, and write them out in the apprpriate syntax for
    a particular file template.

    :param value: The boolean value to be converted
    :param true: The value to return if ``value`` is ``True``
    :param false: The value to return if ``value`` is ``False``

    In this example, a pillar named ``smtp:encrypted`` stores a boolean
    value, but the template that uses that value needs ``yes`` or ``no``
    to be written, based on the boolean value.

    *Note: this is written on two lines for clarity. The same result
    could be achieved in one line.*

    .. code-block:: jinja

        {% set encrypted = salt[pillar.get]('smtp:encrypted', false) %}
        use_tls: {{ salt['slsutil.boolstr'](encrypted, 'yes', 'no') }}

    Result (assuming the value is ``True``):

    .. code-block:: none

        use_tls: yes

    """

    if value:
        return true

    return false
<|endoftext|>


<commit_before>'''
A module to manage software on Windows

:depends:   - pythoncom
            - win32com
            - win32con
            - win32api
'''

# Import third party libs
try:
    import pythoncom
    import win32com.client
    import win32api
    import win32con
    HAS_DEPENDENCIES = True
except ImportError:
    HAS_DEPENDENCIES = False

# Import python libs
import logging
import msgpack
import os
import salt.utils
from distutils.version import LooseVersion

log = logging.getLogger(__name__)

def __virtual__():
    '''
    Set the virtual pkg module if the os is Windows
    '''
    if salt.utils.is_windows() and HAS_DEPENDENCIES:
        return 'pkg'
    return False

def _list_removed(old, new):
    '''
    List the packages which have been removed between the two package objects
    '''
    pkgs = []
    for pkg in old:
        if pkg not in new:
            pkgs.append(pkg)
    return pkgs

def available_version(name):
    '''
    The available version of the package in the repository

    CLI Example::

        salt '*' pkg.available_version <package name>
    '''
    ret = {}
    pkginfo = _get_package_info(name)
    if not pkginfo:
        return ret
    for key in _get_package_info(name):
        ret[key] = pkginfo[key]['full_name']
    return ret

def upgrade_available(name):
    '''
    Check whether or not an upgrade is available for a given package

    CLI Example::

        salt '*' pkg.upgrade_available <package name>
    '''
    log.warning('pkg.upgrade_available not implemented on Windows yet')
    return False

def list_upgrades(refresh=True):
    '''
    List all available package upgrades on this system

    CLI Example::

        salt '*' pkg.list_upgrades
    '''
    log.warning('pkg.list_upgrades not implemented on Windows yet')

    # Uncomment the below once pkg.list_upgrades has been implemented

    # Catch both boolean input from state and string input from CLI
    #if refresh is True or str(refresh).lower() == 'true':
    #    refresh_db()
    return {}

def version(name):
    '''
    Returns a version if the package is installed, else returns an empty string

    CLI Example::

        salt '*' pkg.version <package name>
    '''
    pkgs = list_pkgs()
    if name in pkgs:
        return pkgs[name]
    else:
        return ''

def list_pkgs(*args):
    '''
        List the packages currently installed in a dict::

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

        CLI Example::

            salt '*' pkg.list_pkgs
    '''
    pythoncom.CoInitialize()
    if len(args) == 0:
        pkgs = dict(
            list(_get_reg_software().items()) +
            list(_get_msi_software().items()))
    else:
        # get package version for each package in *args
        pkgs = {}
        for arg in args:
            pkgs.update(_search_software(arg))
    pythoncom.CoUninitialize()
    return pkgs

def _search_software(target):
    '''
    This searches the msi product databases for name matches
    of the list of target products, it will return a dict with
    values added to the list passed in
    '''
    search_results = {}
    software = dict(
        list(_get_reg_software().items()) +
        list(_get_msi_software().items()))
    for key, value in software.items():
        if key is not None:
            if target.lower() in key.lower():
                search_results[key] = value
    return search_results

def _get_msi_software():
    '''
    This searches the msi product databases and returns a dict keyed
    on the product name and all the product properties in another dict
    '''
    win32_products = {}
    this_computer = "."
    wmi_service = win32com.client.Dispatch("WbemScripting.SWbemLocator")
    swbem_services = wmi_service.ConnectServer(this_computer, "root\cimv2")
    products = swbem_services.ExecQuery("Select * from Win32_Product")
    for product in products:
        prd_name = product.Name.encode('ascii', 'ignore')
        prd_ver = product.Version.encode('ascii', 'ignore')
        win32_products[prd_name] = prd_ver
    return win32_products

def _get_reg_software():
    '''
    This searches the uninstall keys in the registry to find
    a match in the sub keys, it will return a dict with the
    display name as the key and the version as the value
    '''
    reg_software = {}
    #This is a list of default OS reg entries that don't seem to be installed
    #software and no version information exists on any of these items
    ignore_list = ['AddressBook',
                   'Connection Manager',
                   'DirectDrawEx',
                   'Fontcore',
                   'IE40',
                   'IE4Data',
                   'IE5BAKEX',
                   'IEData',
                   'MobileOptionPack',
                   'SchedulingAgent',
                   'WIC'
                   ]
    #attempt to corral the wild west of the multiple ways to install
    #software in windows
    reg_entries = dict(list(_get_user_keys().items()) +
                       list(_get_machine_keys().items()))
    for reg_hive, reg_keys in reg_entries.items():
        for reg_key in reg_keys:
            try:
                reg_handle = win32api.RegOpenKeyEx(
                    reg_hive,
                    reg_key,
                    0,
                    win32con.KEY_READ)
            except Exception:
                pass
                #Unsinstall key may not exist for all users
            for name, num, blank, time in win32api.RegEnumKeyEx(reg_handle):
                if name[0] == '{':
                    break
                prd_uninst_key = "\\".join([reg_key, name])
                #These reg values aren't guaranteed to exist
                prd_name = _get_reg_value(
                    reg_hive,
                    prd_uninst_key,
                    "DisplayName")
                prd_ver = _get_reg_value(
                    reg_hive,
                    prd_uninst_key,
                    "DisplayVersion")
                if not name in ignore_list:
                    if not prd_name == 'Not Found':
                        reg_software[prd_name] = prd_ver
    return reg_software

def _get_machine_keys():
    '''
    This will return the hive 'const' value and some registry keys where
    installed software information has been known to exist for the
    HKEY_LOCAL_MACHINE hive
    '''
    machine_hive_and_keys = {}
    machine_keys = [
        "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall",
        "Software\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
    ]
    machine_hive = win32con.HKEY_LOCAL_MACHINE
    machine_hive_and_keys[machine_hive] = machine_keys
    return machine_hive_and_keys

def _get_user_keys():
    '''
    This will return the hive 'const' value and some registry keys where
    installed software information has been known to exist for the
    HKEY_USERS hive
    '''
    user_hive_and_keys = {}
    user_keys = []
    users_hive = win32con.HKEY_USERS
    #skip some built in and default users since software information in these
    #keys is limited
    skip_users = ['.DEFAULT',
                  'S-1-5-18',
                  'S-1-5-19',
                  'S-1-5-20']
    sw_uninst_key = "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
    reg_handle = win32api.RegOpenKeyEx(
        users_hive,
        '',
        0,
        win32con.KEY_READ)
    for name, num, blank, time in win32api.RegEnumKeyEx(reg_handle):
        #this is some identical key of a sid that contains some software names
        #but no detailed information about the software installed for that user
        if '_Classes' in name:
            break
        if name not in skip_users:
            usr_sw_uninst_key = "\\".join([name, sw_uninst_key])
            user_keys.append(usr_sw_uninst_key)
    user_hive_and_keys[users_hive] = user_keys
    return user_hive_and_keys

def _get_reg_value(reg_hive, reg_key, value_name=''):
    '''
    Read one value from Windows registry.
    If 'name' is empty string, reads default value.
    '''
    try:
        key_handle = win32api.RegOpenKeyEx(
            reg_hive, reg_key, 0, win32con.KEY_ALL_ACCESS)
        value_data, value_type = win32api.RegQueryValueEx(key_handle,
                                                          value_name)
        win32api.RegCloseKey(key_handle)
    except Exception:
        value_data = 'Not Found'
    return value_data

def refresh_db():
    '''
    Just recheck the repository and return a dict::

        {'<database name>': Bool}

    CLI Example::

        salt '*' pkg.refresh_db
    '''
    repocache = __opts__['win_repo_cachefile']
    cached_repo = __salt__['cp.is_cached'](repocache)
    if not cached_repo:
        # It's not cached. Cache it, mate.
        cached_repo = __salt__['cp.cache_file'](repocache)
        return True
    # Check if the master's cache file has changed
    if __salt__['cp.hash_file'](repocache) !=\
            __salt__['cp.hash_file'](cached_repo):
                cached_repo = __salt__['cp.cache_file'](repocache)
    return True

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

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

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

    CLI Example::

        salt '*' pkg.install <package name>
    '''
    if refresh:
        refresh_db()
    old = list_pkgs()
    pkginfo = _get_package_info(name)
    for pkg in pkginfo.keys():
        if pkginfo[pkg]['full_name'] in old:
            return '{0} already installed'.format(pkginfo[pkg]['full_name'])
    if kwargs.get('version') is not None:
        version = kwargs['version']
    else:
        version = _get_latest_pkg_version(pkginfo)
    if pkginfo[version]['installer'].startswith('salt:') or pkginfo[version]['installer'].startswith('http:') or pkginfo[version]['installer'].startswith('https:') or pkginfo[version]['installer'].startswith('ftp:'):
        cached_pkg = __salt__['cp.is_cached'](pkginfo[version]['installer'])
        if not cached_pkg:
            # It's not cached. Cache it, mate.
            cached_pkg = __salt__['cp.cache_file'](pkginfo[
                                                   version]['installer'])
    else:
        cached_pkg = pkginfo[version]['installer']
    cached_pkg = cached_pkg.replace('/', '\\')
    cmd = '"' + str(cached_pkg) + '"' + str(pkginfo[version]['install_flags'])
    if pkginfo[version]['msiexec']:
        cmd = 'msiexec /i ' + cmd
    stderr = __salt__['cmd.run_all'](cmd).get('stderr', '')
    if stderr:
        log.error(stderr)
    new = list_pkgs()
    return __salt__['pkg_resource.find_changes'](old, new)

def upgrade(refresh=True):
    '''
    Run a full system upgrade

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

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

    CLI Example::

        salt '*' pkg.upgrade
    '''
    log.warning('pkg.upgrade not implemented on Windows yet')

    # Uncomment the below once pkg.upgrade has been implemented

    # Catch both boolean input from state and string input from CLI
    #if refresh is True or str(refresh).lower() == 'true':
    #    refresh_db()
    return {}

def remove(name, version=None, **kwargs):
    '''
    Remove a single package

    Return a list containing the removed packages.

    CLI Example::

        salt '*' pkg.remove <package name>
    '''
    old = list_pkgs()
    pkginfo = _get_package_info(name)
    if not version:
        version = _get_latest_pkg_version(pkginfo)

    if pkginfo[version]['uninstaller'].startswith('salt:'):
        cached_pkg = __salt__['cp.is_cached'](pkginfo[version]['uninstaller'])
        if not cached_pkg:
            # It's not cached. Cache it, mate.
            cached_pkg = __salt__['cp.cache_file'](pkginfo[
                                                   version]['uninstaller'])
    else:
        cached_pkg = pkginfo[version]['uninstaller']
    cached_pkg = cached_pkg.replace('/', '\\')
    if not os.path.exists(os.path.expandvars(cached_pkg)) and '(x86)' in cached_pkg:
        cached_pkg = cached_pkg.replace('(x86)', '')
    cmd = '"' + str(os.path.expandvars(
        cached_pkg)) + '"' + str(pkginfo[version]['uninstall_flags'])
    if pkginfo[version]['msiexec']:
        cmd = 'msiexec /x ' + cmd
    stderr = __salt__['cmd.run_all'](cmd).get('stderr', '')
    if stderr:
        log.error(stderr)
    new = list_pkgs()
    return __salt__['pkg_resource.find_changes'](old, new)

def purge(name, version=None, **kwargs):
    '''
    Recursively remove a package and all dependencies which were installed
    with it

    Return a list containing the removed packages.

    CLI Example::

        salt '*' pkg.purge <package name>
    '''
    return remove(name, version, **kwargs)

def _get_package_info(name):
    '''
    Return package info.
    TODO: Add option for version
    '''
    repocache = __opts__['win_repo_cachefile']
    cached_repo = __salt__['cp.is_cached'](repocache)
    if not cached_repo:
        __salt__['pkg.refresh_db']
    try:
        with salt.utils.fopen(cached_repo, 'r') as repofile:
            try:
                repodata = msgpack.loads(repofile.read()) or {}
            except:
                return 'Windows package repo not available'
    except IOError:
        log.debug('Not able to read repo file')
        return 'Windows package repo not available'
    if not repodata:
        return 'Windows package repo not available'
    if name in repodata:
        return repodata[name]
    else:
        return False  # name, ' is not available.'
    return False  # name, ' is not available.'

def _reverse_cmp_pkg_versions(pkg1, pkg2):
    '''
    Compare software package versions
    '''
    if LooseVersion(pkg1) > LooseVersion(pkg2):
        return 1
    else:
        return -1

def _get_latest_pkg_version(pkginfo):
    if len(pkginfo) == 1:
        return pkginfo.keys().pop()
    pkgkeys = pkginfo.keys()
    return sorted(pkgkeys, cmp=_reverse_cmp_pkg_versions).pop()

def perform_cmp(pkg1='', pkg2=''):
    '''
    Do a cmp-style comparison on two packages. Return -1 if pkg1 < pkg2, 0 if
    pkg1 == pkg2, and 1 if pkg1 > pkg2. Return None if there was a problem
    making the comparison.

    CLI Example::

        salt '*' pkg.perform_cmp '0.2.4-0' '0.2.4.1-0'
        salt '*' pkg.perform_cmp pkg1='0.2.4-0' pkg2='0.2.4.1-0'
    '''
    return __salt__['pkg_resource.perform_cmp'](pkg1=pkg1, pkg2=pkg2)

def compare(pkg1='', oper='==', pkg2=''):
    '''
    Compare two version strings.

    CLI Example::

        salt '*' pkg.compare '0.2.4-0' '<' '0.2.4.1-0'
        salt '*' pkg.compare pkg1='0.2.4-0' oper='<' pkg2='0.2.4.1-0'
    '''
    return __salt__['pkg_resource.compare'](pkg1=pkg1, oper=oper, pkg2=pkg2)
<commit_msg>Use salt.utils.winapi.Com instead of manually<commit_after>'''
A module to manage software on Windows

:depends:   - pythoncom
            - win32com
            - win32con
            - win32api
'''

# Import third party libs
try:
    import pythoncom
    import win32com.client
    import win32api
    import win32con
    HAS_DEPENDENCIES = True
except ImportError:
    HAS_DEPENDENCIES = False

# Import python libs
import logging
import msgpack
import os
import salt.utils
from distutils.version import LooseVersion

log = logging.getLogger(__name__)

def __virtual__():
    '''
    Set the virtual pkg module if the os is Windows
    '''
    if salt.utils.is_windows() and HAS_DEPENDENCIES:
        return 'pkg'
    return False

def _list_removed(old, new):
    '''
    List the packages which have been removed between the two package objects
    '''
    pkgs = []
    for pkg in old:
        if pkg not in new:
            pkgs.append(pkg)
    return pkgs

def available_version(name):
    '''
    The available version of the package in the repository

    CLI Example::

        salt '*' pkg.available_version <package name>
    '''
    ret = {}
    pkginfo = _get_package_info(name)
    if not pkginfo:
        return ret
    for key in _get_package_info(name):
        ret[key] = pkginfo[key]['full_name']
    return ret

def upgrade_available(name):
    '''
    Check whether or not an upgrade is available for a given package

    CLI Example::

        salt '*' pkg.upgrade_available <package name>
    '''
    log.warning('pkg.upgrade_available not implemented on Windows yet')
    return False

def list_upgrades(refresh=True):
    '''
    List all available package upgrades on this system

    CLI Example::

        salt '*' pkg.list_upgrades
    '''
    log.warning('pkg.list_upgrades not implemented on Windows yet')

    # Uncomment the below once pkg.list_upgrades has been implemented

    # Catch both boolean input from state and string input from CLI
    #if refresh is True or str(refresh).lower() == 'true':
    #    refresh_db()
    return {}

def version(name):
    '''
    Returns a version if the package is installed, else returns an empty string

    CLI Example::

        salt '*' pkg.version <package name>
    '''
    pkgs = list_pkgs()
    if name in pkgs:
        return pkgs[name]
    else:
        return ''

def list_pkgs(*args):
    '''
        List the packages currently installed in a dict::

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

        CLI Example::

            salt '*' pkg.list_pkgs
    '''
    with salt.utils.winapi.Com():
        if len(args) == 0:
            pkgs = dict(
                list(_get_reg_software().items()) +
                list(_get_msi_software().items()))
        else:
            # get package version for each package in *args
            pkgs = {}
            for arg in args:
                pkgs.update(_search_software(arg))
    return pkgs

def _search_software(target):
    '''
    This searches the msi product databases for name matches
    of the list of target products, it will return a dict with
    values added to the list passed in
    '''
    search_results = {}
    software = dict(
        list(_get_reg_software().items()) +
        list(_get_msi_software().items()))
    for key, value in software.items():
        if key is not None:
            if target.lower() in key.lower():
                search_results[key] = value
    return search_results

def _get_msi_software():
    '''
    This searches the msi product databases and returns a dict keyed
    on the product name and all the product properties in another dict
    '''
    win32_products = {}
    this_computer = "."
    wmi_service = win32com.client.Dispatch("WbemScripting.SWbemLocator")
    swbem_services = wmi_service.ConnectServer(this_computer, "root\cimv2")
    products = swbem_services.ExecQuery("Select * from Win32_Product")
    for product in products:
        prd_name = product.Name.encode('ascii', 'ignore')
        prd_ver = product.Version.encode('ascii', 'ignore')
        win32_products[prd_name] = prd_ver
    return win32_products

def _get_reg_software():
    '''
    This searches the uninstall keys in the registry to find
    a match in the sub keys, it will return a dict with the
    display name as the key and the version as the value
    '''
    reg_software = {}
    #This is a list of default OS reg entries that don't seem to be installed
    #software and no version information exists on any of these items
    ignore_list = ['AddressBook',
                   'Connection Manager',
                   'DirectDrawEx',
                   'Fontcore',
                   'IE40',
                   'IE4Data',
                   'IE5BAKEX',
                   'IEData',
                   'MobileOptionPack',
                   'SchedulingAgent',
                   'WIC'
                   ]
    #attempt to corral the wild west of the multiple ways to install
    #software in windows
    reg_entries = dict(list(_get_user_keys().items()) +
                       list(_get_machine_keys().items()))
    for reg_hive, reg_keys in reg_entries.items():
        for reg_key in reg_keys:
            try:
                reg_handle = win32api.RegOpenKeyEx(
                    reg_hive,
                    reg_key,
                    0,
                    win32con.KEY_READ)
            except Exception:
                pass
                #Unsinstall key may not exist for all users
            for name, num, blank, time in win32api.RegEnumKeyEx(reg_handle):
                if name[0] == '{':
                    break
                prd_uninst_key = "\\".join([reg_key, name])
                #These reg values aren't guaranteed to exist
                prd_name = _get_reg_value(
                    reg_hive,
                    prd_uninst_key,
                    "DisplayName")
                prd_ver = _get_reg_value(
                    reg_hive,
                    prd_uninst_key,
                    "DisplayVersion")
                if not name in ignore_list:
                    if not prd_name == 'Not Found':
                        reg_software[prd_name] = prd_ver
    return reg_software

def _get_machine_keys():
    '''
    This will return the hive 'const' value and some registry keys where
    installed software information has been known to exist for the
    HKEY_LOCAL_MACHINE hive
    '''
    machine_hive_and_keys = {}
    machine_keys = [
        "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall",
        "Software\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
    ]
    machine_hive = win32con.HKEY_LOCAL_MACHINE
    machine_hive_and_keys[machine_hive] = machine_keys
    return machine_hive_and_keys

def _get_user_keys():
    '''
    This will return the hive 'const' value and some registry keys where
    installed software information has been known to exist for the
    HKEY_USERS hive
    '''
    user_hive_and_keys = {}
    user_keys = []
    users_hive = win32con.HKEY_USERS
    #skip some built in and default users since software information in these
    #keys is limited
    skip_users = ['.DEFAULT',
                  'S-1-5-18',
                  'S-1-5-19',
                  'S-1-5-20']
    sw_uninst_key = "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
    reg_handle = win32api.RegOpenKeyEx(
        users_hive,
        '',
        0,
        win32con.KEY_READ)
    for name, num, blank, time in win32api.RegEnumKeyEx(reg_handle):
        #this is some identical key of a sid that contains some software names
        #but no detailed information about the software installed for that user
        if '_Classes' in name:
            break
        if name not in skip_users:
            usr_sw_uninst_key = "\\".join([name, sw_uninst_key])
            user_keys.append(usr_sw_uninst_key)
    user_hive_and_keys[users_hive] = user_keys
    return user_hive_and_keys

def _get_reg_value(reg_hive, reg_key, value_name=''):
    '''
    Read one value from Windows registry.
    If 'name' is empty string, reads default value.
    '''
    try:
        key_handle = win32api.RegOpenKeyEx(
            reg_hive, reg_key, 0, win32con.KEY_ALL_ACCESS)
        value_data, value_type = win32api.RegQueryValueEx(key_handle,
                                                          value_name)
        win32api.RegCloseKey(key_handle)
    except Exception:
        value_data = 'Not Found'
    return value_data

def refresh_db():
    '''
    Just recheck the repository and return a dict::

        {'<database name>': Bool}

    CLI Example::

        salt '*' pkg.refresh_db
    '''
    repocache = __opts__['win_repo_cachefile']
    cached_repo = __salt__['cp.is_cached'](repocache)
    if not cached_repo:
        # It's not cached. Cache it, mate.
        cached_repo = __salt__['cp.cache_file'](repocache)
        return True
    # Check if the master's cache file has changed
    if __salt__['cp.hash_file'](repocache) !=\
            __salt__['cp.hash_file'](cached_repo):
                cached_repo = __salt__['cp.cache_file'](repocache)
    return True

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

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

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

    CLI Example::

        salt '*' pkg.install <package name>
    '''
    if refresh:
        refresh_db()
    old = list_pkgs()
    pkginfo = _get_package_info(name)
    for pkg in pkginfo.keys():
        if pkginfo[pkg]['full_name'] in old:
            return '{0} already installed'.format(pkginfo[pkg]['full_name'])
    if kwargs.get('version') is not None:
        version = kwargs['version']
    else:
        version = _get_latest_pkg_version(pkginfo)
    if pkginfo[version]['installer'].startswith('salt:') or pkginfo[version]['installer'].startswith('http:') or pkginfo[version]['installer'].startswith('https:') or pkginfo[version]['installer'].startswith('ftp:'):
        cached_pkg = __salt__['cp.is_cached'](pkginfo[version]['installer'])
        if not cached_pkg:
            # It's not cached. Cache it, mate.
            cached_pkg = __salt__['cp.cache_file'](pkginfo[
                                                   version]['installer'])
    else:
        cached_pkg = pkginfo[version]['installer']
    cached_pkg = cached_pkg.replace('/', '\\')
    cmd = '"' + str(cached_pkg) + '"' + str(pkginfo[version]['install_flags'])
    if pkginfo[version]['msiexec']:
        cmd = 'msiexec /i ' + cmd
    stderr = __salt__['cmd.run_all'](cmd).get('stderr', '')
    if stderr:
        log.error(stderr)
    new = list_pkgs()
    return __salt__['pkg_resource.find_changes'](old, new)

def upgrade(refresh=True):
    '''
    Run a full system upgrade

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

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

    CLI Example::

        salt '*' pkg.upgrade
    '''
    log.warning('pkg.upgrade not implemented on Windows yet')

    # Uncomment the below once pkg.upgrade has been implemented

    # Catch both boolean input from state and string input from CLI
    #if refresh is True or str(refresh).lower() == 'true':
    #    refresh_db()
    return {}

def remove(name, version=None, **kwargs):
    '''
    Remove a single package

    Return a list containing the removed packages.

    CLI Example::

        salt '*' pkg.remove <package name>
    '''
    old = list_pkgs()
    pkginfo = _get_package_info(name)
    if not version:
        version = _get_latest_pkg_version(pkginfo)

    if pkginfo[version]['uninstaller'].startswith('salt:'):
        cached_pkg = __salt__['cp.is_cached'](pkginfo[version]['uninstaller'])
        if not cached_pkg:
            # It's not cached. Cache it, mate.
            cached_pkg = __salt__['cp.cache_file'](pkginfo[
                                                   version]['uninstaller'])
    else:
        cached_pkg = pkginfo[version]['uninstaller']
    cached_pkg = cached_pkg.replace('/', '\\')
    if not os.path.exists(os.path.expandvars(cached_pkg)) and '(x86)' in cached_pkg:
        cached_pkg = cached_pkg.replace('(x86)', '')
    cmd = '"' + str(os.path.expandvars(
        cached_pkg)) + '"' + str(pkginfo[version]['uninstall_flags'])
    if pkginfo[version]['msiexec']:
        cmd = 'msiexec /x ' + cmd
    stderr = __salt__['cmd.run_all'](cmd).get('stderr', '')
    if stderr:
        log.error(stderr)
    new = list_pkgs()
    return __salt__['pkg_resource.find_changes'](old, new)

def purge(name, version=None, **kwargs):
    '''
    Recursively remove a package and all dependencies which were installed
    with it

    Return a list containing the removed packages.

    CLI Example::

        salt '*' pkg.purge <package name>
    '''
    return remove(name, version, **kwargs)

def _get_package_info(name):
    '''
    Return package info.
    TODO: Add option for version
    '''
    repocache = __opts__['win_repo_cachefile']
    cached_repo = __salt__['cp.is_cached'](repocache)
    if not cached_repo:
        __salt__['pkg.refresh_db']
    try:
        with salt.utils.fopen(cached_repo, 'r') as repofile:
            try:
                repodata = msgpack.loads(repofile.read()) or {}
            except:
                return 'Windows package repo not available'
    except IOError:
        log.debug('Not able to read repo file')
        return 'Windows package repo not available'
    if not repodata:
        return 'Windows package repo not available'
    if name in repodata:
        return repodata[name]
    else:
        return False  # name, ' is not available.'
    return False  # name, ' is not available.'

def _reverse_cmp_pkg_versions(pkg1, pkg2):
    '''
    Compare software package versions
    '''
    if LooseVersion(pkg1) > LooseVersion(pkg2):
        return 1
    else:
        return -1

def _get_latest_pkg_version(pkginfo):
    if len(pkginfo) == 1:
        return pkginfo.keys().pop()
    pkgkeys = pkginfo.keys()
    return sorted(pkgkeys, cmp=_reverse_cmp_pkg_versions).pop()

def perform_cmp(pkg1='', pkg2=''):
    '''
    Do a cmp-style comparison on two packages. Return -1 if pkg1 < pkg2, 0 if
    pkg1 == pkg2, and 1 if pkg1 > pkg2. Return None if there was a problem
    making the comparison.

    CLI Example::

        salt '*' pkg.perform_cmp '0.2.4-0' '0.2.4.1-0'
        salt '*' pkg.perform_cmp pkg1='0.2.4-0' pkg2='0.2.4.1-0'
    '''
    return __salt__['pkg_resource.perform_cmp'](pkg1=pkg1, pkg2=pkg2)

def compare(pkg1='', oper='==', pkg2=''):
    '''
    Compare two version strings.

    CLI Example::

        salt '*' pkg.compare '0.2.4-0' '<' '0.2.4.1-0'
        salt '*' pkg.compare pkg1='0.2.4-0' oper='<' pkg2='0.2.4.1-0'
    '''
    return __salt__['pkg_resource.compare'](pkg1=pkg1, oper=oper, pkg2=pkg2)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
'''
Management of Block Devices

A state module to manage blockdevices

.. code-block:: yaml

    /dev/sda:
      blockdev.tuned:
        - read-only: True

    master-data:
      blockdev.tuned:
        - name: /dev/vg/master-data
        - read-only: True
        - read-ahead: 1024

.. versionadded:: 2014.7.0
'''
from __future__ import absolute_import

# Import python libs
import os
import os.path
import time
import logging

# Import salt libs
import salt.utils
from salt.ext.six.moves import range

__virtualname__ = 'blockdev'

# Init logger
log = logging.getLogger(__name__)

def __virtual__():
    '''
    Only load this module if the disk execution module is available
    '''
    if 'disk.tune' in __salt__:
        return __virtualname__
    return (False, ('Cannot load the {0} state module: '
                    'disk execution module not found'.format(__virtualname__)))

def tuned(name, **kwargs):
    '''
    Manage options of block device

    name
        The name of the block device

    opts:
      - read-ahead
          Read-ahead buffer size

      - filesystem-read-ahead
          Filesystem Read-ahead buffer size

      - read-only
          Set Read-Only

      - read-write
          Set Read-Write
    '''

    ret = {'changes': {},
           'comment': '',
           'name': name,
           'result': True}

    kwarg_map = {'read-ahead': 'getra',
                 'filesystem-read-ahead': 'getfra',
                 'read-only': 'getro',
                 'read-write': 'getro'}

    if not __salt__['file.is_blkdev']:
        ret['comment'] = ('Changes to {0} cannot be applied. '
                          'Not a block device. ').format(name)
    elif __opts__['test']:
        ret['comment'] = 'Changes to {0} will be applied '.format(name)
        ret['result'] = None
        return ret
    else:
        current = __salt__['disk.dump'](name)
        changes = __salt__['disk.tune'](name, **kwargs)
        changeset = {}
        for key in kwargs:
            if key in kwarg_map:
                switch = kwarg_map[key]
                if current[switch] != changes[switch]:
                    if isinstance(kwargs[key], bool):
                        old = (current[switch] == '1')
                        new = (changes[switch] == '1')
                    else:
                        old = current[switch]
                        new = changes[switch]
                    if key == 'read-write':
                        old = not old
                        new = not new
                    changeset[key] = 'Changed from {0} to {1}'.format(old, new)
        if changes:
            if changeset:
                ret['comment'] = ('Block device {0} '
                                  'successfully modified ').format(name)
                ret['changes'] = changeset
            else:
                ret['comment'] = 'Block device {0} already in correct state'.format(name)
        else:
            ret['comment'] = 'Failed to modify block device {0}'.format(name)
            ret['result'] = False
    return ret

def formatted(name, fs_type='ext4', force=False, **kwargs):
    '''
    Manage filesystems of partitions.

    name
        The name of the block device

    fs_type
        The filesystem it should be formatted as

    force
        Force mke2fs to create a filesystem, even if the specified device is
        not a partition on a block special device. This option is only enabled
        for ext and xfs filesystems

        This option is dangerous, use it with caution.

        .. versionadded:: 2016.11.0
    '''
    ret = {'changes': {},
           'comment': '{0} already formatted with {1}'.format(name, fs_type),
           'name': name,
           'result': False}

    if not os.path.exists(name):
        ret['comment'] = '{0} does not exist'.format(name)
        return ret

    current_fs = _checkblk(name)

    if current_fs == fs_type:
        ret['result'] = True
        return ret
    elif not salt.utils.which('mkfs.{0}'.format(fs_type)):
        ret['comment'] = 'Invalid fs_type: {0}'.format(fs_type)
        ret['result'] = False
        return ret
    elif __opts__['test']:
        ret['comment'] = 'Changes to {0} will be applied '.format(name)
        ret['result'] = None
        return ret

    __salt__['disk.format_'](name, fs_type, force=force, **kwargs)

    # Repeat fstype check up to 10 times with 3s sleeping between each
    # to avoid detection failing although mkfs has succeeded
    # see https://github.com/saltstack/salt/issues/25775i
    # This retry maybe superfluous - switching to blkid
    for i in range(10):

        log.info('Check blk fstype attempt %s of 10', str(i+1))
        current_fs = _checkblk(name)

        if current_fs == fs_type:
            ret['comment'] = ('{0} has been formatted '
                              'with {1}').format(name, fs_type)
            ret['changes'] = {'new': fs_type, 'old': current_fs}
            ret['result'] = True
            return ret

        if current_fs == '':
            log.info('Waiting 3s before next check')
            time.sleep(3)
        else:
            break

    ret['comment'] = 'Failed to format {0}'.format(name)
    ret['result'] = False
    return ret

def _checkblk(name):
    '''
    Check if the blk exists and return its fstype if ok
    '''

    blk = __salt__['cmd.run']('blkid -o value -s TYPE {0}'.format(name))
    return '' if not blk else blk
<commit_msg>addresses issue #43307, disk.format_ to disk.format<commit_after># -*- coding: utf-8 -*-
'''
Management of Block Devices

A state module to manage blockdevices

.. code-block:: yaml

    /dev/sda:
      blockdev.tuned:
        - read-only: True

    master-data:
      blockdev.tuned:
        - name: /dev/vg/master-data
        - read-only: True
        - read-ahead: 1024

.. versionadded:: 2014.7.0
'''
from __future__ import absolute_import

# Import python libs
import os
import os.path
import time
import logging

# Import salt libs
import salt.utils
from salt.ext.six.moves import range

__virtualname__ = 'blockdev'

# Init logger
log = logging.getLogger(__name__)

def __virtual__():
    '''
    Only load this module if the disk execution module is available
    '''
    if 'disk.tune' in __salt__:
        return __virtualname__
    return (False, ('Cannot load the {0} state module: '
                    'disk execution module not found'.format(__virtualname__)))

def tuned(name, **kwargs):
    '''
    Manage options of block device

    name
        The name of the block device

    opts:
      - read-ahead
          Read-ahead buffer size

      - filesystem-read-ahead
          Filesystem Read-ahead buffer size

      - read-only
          Set Read-Only

      - read-write
          Set Read-Write
    '''

    ret = {'changes': {},
           'comment': '',
           'name': name,
           'result': True}

    kwarg_map = {'read-ahead': 'getra',
                 'filesystem-read-ahead': 'getfra',
                 'read-only': 'getro',
                 'read-write': 'getro'}

    if not __salt__['file.is_blkdev']:
        ret['comment'] = ('Changes to {0} cannot be applied. '
                          'Not a block device. ').format(name)
    elif __opts__['test']:
        ret['comment'] = 'Changes to {0} will be applied '.format(name)
        ret['result'] = None
        return ret
    else:
        current = __salt__['disk.dump'](name)
        changes = __salt__['disk.tune'](name, **kwargs)
        changeset = {}
        for key in kwargs:
            if key in kwarg_map:
                switch = kwarg_map[key]
                if current[switch] != changes[switch]:
                    if isinstance(kwargs[key], bool):
                        old = (current[switch] == '1')
                        new = (changes[switch] == '1')
                    else:
                        old = current[switch]
                        new = changes[switch]
                    if key == 'read-write':
                        old = not old
                        new = not new
                    changeset[key] = 'Changed from {0} to {1}'.format(old, new)
        if changes:
            if changeset:
                ret['comment'] = ('Block device {0} '
                                  'successfully modified ').format(name)
                ret['changes'] = changeset
            else:
                ret['comment'] = 'Block device {0} already in correct state'.format(name)
        else:
            ret['comment'] = 'Failed to modify block device {0}'.format(name)
            ret['result'] = False
    return ret

def formatted(name, fs_type='ext4', force=False, **kwargs):
    '''
    Manage filesystems of partitions.

    name
        The name of the block device

    fs_type
        The filesystem it should be formatted as

    force
        Force mke2fs to create a filesystem, even if the specified device is
        not a partition on a block special device. This option is only enabled
        for ext and xfs filesystems

        This option is dangerous, use it with caution.

        .. versionadded:: 2016.11.0
    '''
    ret = {'changes': {},
           'comment': '{0} already formatted with {1}'.format(name, fs_type),
           'name': name,
           'result': False}

    if not os.path.exists(name):
        ret['comment'] = '{0} does not exist'.format(name)
        return ret

    current_fs = _checkblk(name)

    if current_fs == fs_type:
        ret['result'] = True
        return ret
    elif not salt.utils.which('mkfs.{0}'.format(fs_type)):
        ret['comment'] = 'Invalid fs_type: {0}'.format(fs_type)
        ret['result'] = False
        return ret
    elif __opts__['test']:
        ret['comment'] = 'Changes to {0} will be applied '.format(name)
        ret['result'] = None
        return ret

    __salt__['disk.format'](name, fs_type, force=force, **kwargs)

    # Repeat fstype check up to 10 times with 3s sleeping between each
    # to avoid detection failing although mkfs has succeeded
    # see https://github.com/saltstack/salt/issues/25775i
    # This retry maybe superfluous - switching to blkid
    for i in range(10):

        log.info('Check blk fstype attempt %s of 10', str(i+1))
        current_fs = _checkblk(name)

        if current_fs == fs_type:
            ret['comment'] = ('{0} has been formatted '
                              'with {1}').format(name, fs_type)
            ret['changes'] = {'new': fs_type, 'old': current_fs}
            ret['result'] = True
            return ret

        if current_fs == '':
            log.info('Waiting 3s before next check')
            time.sleep(3)
        else:
            break

    ret['comment'] = 'Failed to format {0}'.format(name)
    ret['result'] = False
    return ret

def _checkblk(name):
    '''
    Check if the blk exists and return its fstype if ok
    '''

    blk = __salt__['cmd.run']('blkid -o value -s TYPE {0}'.format(name))
    return '' if not blk else blk
<|endoftext|>