Change package state in the dpkg database.

    The state can be any one of, documented in ``dpkg(1)``:

    - install
    - hold
    - deinstall
    - purge

    This command is commonly used to mark specific packages to be held from
    being upgraded, that is, to be kept at a certain version. When a state is
    changed to anything but being held, then it is typically followed by
    ``apt-get -u dselect-upgrade``.

    Note: Be careful with the ``clear`` argument, since it will start
    with setting all packages to deinstall state.

    Returns a dict of dicts containing the package names, and the new and old
    versions:

    .. code-block:: python

        {'<host>':
            {'<package>': {'new': '<new-state>',
                           'old': '<old-state>'}
            },
            ...
        }

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.set_selections selection='{"install": ["netcat"]}'
        salt '*' pkg.set_selections selection='{"hold": ["openssh-server", "openssh-client"]}'
        salt '*' pkg.set_selections salt://path/to/file
        salt '*' pkg.set_selections salt://path/to/file clear=True

def set_selections(path=None, selection=None, clear=False, saltenv='base'):
    '''
    Change package state in the dpkg database.

    The state can be any one of, documented in ``dpkg(1)``:

    - install
    - hold
    - deinstall
    - purge

    This command is commonly used to mark specific packages to be held from
    being upgraded, that is, to be kept at a certain version. When a state is
    changed to anything but being held, then it is typically followed by
    ``apt-get -u dselect-upgrade``.

    Note: Be careful with the ``clear`` argument, since it will start
    with setting all packages to deinstall state.

    Returns a dict of dicts containing the package names, and the new and old
    versions:

    .. code-block:: python

        {'<host>':
            {'<package>': {'new': '<new-state>',
                           'old': '<old-state>'}
            },
            ...
        }

    CLI Example:

    .. code-block:: bash

        salt '*' pkg.set_selections selection='{"install": ["netcat"]}'
        salt '*' pkg.set_selections selection='{"hold": ["openssh-server", "openssh-client"]}'
        salt '*' pkg.set_selections salt://path/to/file
        salt '*' pkg.set_selections salt://path/to/file clear=True
    '''
    ret = {}
    if not path and not selection:
        return ret
    if path and selection:
        err = ('The \'selection\' and \'path\' arguments to '
               'pkg.set_selections are mutually exclusive, and cannot be '
               'specified together')
        raise SaltInvocationError(err)

    if isinstance(selection, six.string_types):
        try:
            selection = salt.utils.yaml.safe_load(selection)
        except (salt.utils.yaml.parser.ParserError, salt.utils.yaml.scanner.ScannerError) as exc:
            raise SaltInvocationError(
                'Improperly-formatted selection: {0}'.format(exc)
            )

    if path:
        path = __salt__['cp.cache_file'](path, saltenv)
        with salt.utils.files.fopen(path, 'r') as ifile:
            content = [salt.utils.stringutils.to_unicode(x)
                       for x in ifile.readlines()]
        selection = _parse_selections(content)

    if selection:
        valid_states = ('install', 'hold', 'deinstall', 'purge')
        bad_states = [x for x in selection if x not in valid_states]
        if bad_states:
            raise SaltInvocationError(
                'Invalid state(s): {0}'.format(', '.join(bad_states))
            )

        if clear:
            cmd = ['dpkg', '--clear-selections']
            if not __opts__['test']:
                result = _call_apt(cmd, scope=False)
                if result['retcode'] != 0:
                    err = ('Running dpkg --clear-selections failed: '
                           '{0}'.format(result['stderr']))
                    log.error(err)
                    raise CommandExecutionError(err)

        sel_revmap = {}
        for _state, _pkgs in six.iteritems(get_selections()):
            sel_revmap.update(dict((_pkg, _state) for _pkg in _pkgs))

        for _state, _pkgs in six.iteritems(selection):
            for _pkg in _pkgs:
                if _state == sel_revmap.get(_pkg):
                    continue
                cmd = ['dpkg', '--set-selections']
                cmd_in = '{0} {1}'.format(_pkg, _state)
                if not __opts__['test']:
                    result = _call_apt(cmd, scope=False, stdin=cmd_in)
                    if result['retcode'] != 0:
                        log.error(
                            'failed to set state %s for package %s',
                            _state, _pkg
                        )
                    else:
                        ret[_pkg] = {'old': sel_revmap.get(_pkg),
                                     'new': _state}
    return ret