# Buggy code
def group_installed(name, skip=None, include=None, **kwargs):
    """
    .. versionadded:: 2015.8.0

    Ensure that an entire package group is installed. This state is currently
    only supported for the :mod:`yum <salt.modules.yumpkg>` package manager.

    skip
        Packages that would normally be installed by the package group
        ("default" packages), which should not be installed.

        .. code-block:: yaml

            Load Balancer:
              pkg.group_installed:
                - skip:
                  - piranha

    include
        Packages which are included in a group, which would not normally be
        installed by a ``yum groupinstall`` ("optional" packages). Note that
        this will not enforce group membership; if you include packages which
        are not members of the specified groups, they will still be installed.

        .. code-block:: yaml

            Load Balancer:
              pkg.group_installed:
                - include:
                  - haproxy

        .. versionchanged:: 2016.3.0
            This option can no longer be passed as a comma-separated list, it
            must now be passed as a list (as shown in the above example).

    .. note::
        Because this is essentially a wrapper around :py:func:`pkg.install
        <salt.modules.yumpkg.install>`, any argument which can be passed to
        pkg.install may also be included here, and it will be passed on to the
        call to :py:func:`pkg.install <salt.modules.yumpkg.install>`.
    """
    ret = {"name": name, "changes": {}, "result": False, "comment": ""}

    if "pkg.group_diff" not in __salt__:
        ret["comment"] = "pkg.group_install not available for this platform"
        return ret

    if skip is None:
        skip = []
    else:
        if not isinstance(skip, list):
            ret["comment"] = "skip must be formatted as a list"
            return ret
        for idx, item in enumerate(skip):
            if not isinstance(item, six.string_types):
                skip[idx] = str(item)

    if include is None:
        include = []
    else:
        if not isinstance(include, list):
            ret["comment"] = "include must be formatted as a list"
            return ret
        for idx, item in enumerate(include):
            if not isinstance(item, six.string_types):
                include[idx] = str(item)

    diff = __salt__["pkg.group_diff"](name)
    mandatory = diff["mandatory"]["installed"] + diff["mandatory"]["not installed"]

    invalid_skip = [x for x in mandatory if x in skip]
    if invalid_skip:
        ret["comment"] = (
            "The following mandatory packages cannot be skipped: {0}".format(
                ", ".join(invalid_skip)
            )
        )
        return ret

    targets = diff["mandatory"]["not installed"]
    targets.extend([x for x in diff["default"]["not installed"] if x not in skip])
    targets.extend(include)

    if not targets:
        ret["result"] = True
        ret["comment"] = "Group '{0}' is already installed".format(name)
        return ret

    partially_installed = (
        diff["mandatory"]["installed"]
        or diff["default"]["installed"]
        or diff["optional"]["installed"]
    )

    if __opts__["test"]:
        ret["result"] = None
        if partially_installed:
            ret["comment"] = (
                "Group '{0}' is partially installed and will be updated".format(name)
            )
        else:
            ret["comment"] = "Group '{0}' will be installed".format(name)
        return ret

    try:
        ret["changes"] = __salt__["pkg.install"](pkgs=targets, **kwargs)
    except CommandExecutionError as exc:
        ret = {"name": name, "result": False}
        if exc.info:
            # Get information for state return from the exception.
            ret["changes"] = exc.info.get("changes", {})
            ret["comment"] = exc.strerror_without_changes
        else:
            ret["changes"] = {}
            ret["comment"] = (
                "An error was encountered while "
                "installing/updating group '{0}': {1}".format(name, exc)
            )
        return ret

    failed = [x for x in targets if x not in __salt__["pkg.list_pkgs"]()]
    if failed:
        ret["comment"] = "Failed to install the following packages: {0}".format(
            ", ".join(failed)
        )
        return ret

    ret["result"] = True
    ret["comment"] = "Group '{0}' was {1}".format(
        name, "updated" if partially_installed else "installed"
    )
    return ret


# Traceback (CommandExecutionError)
MATE Desktop:
pkg.group_installed:
- skip:
- admin-tools

[DEBUG   ] LazyLoaded config.get
[DEBUG   ] Results of YAML rendering:
OrderedDict([('xfce', 'pkg.group_installed'), ('MATE Desktop', OrderedDict([('pkg.group_installed', [OrderedDict([('skip', ['admin-tools'])])])])), ('install-cinnamon-for-remlinux', OrderedDict([('pkg.installed', [OrderedDict([('pkgs', ['cinnamon'])])])]))])
[PROFILE ] Time (in seconds) to render '/var/cache/salt/minion/files/extra-desktops/init.sls' using 'yaml' renderer: 0.00969314575195
[DEBUG   ] LazyLoaded pkg.install
[DEBUG   ] LazyLoaded pkg.group_installed
[DEBUG   ] You should upgrade pyOpenSSL to at least 0.14.1 to enable the use of X509 extensions in the tls module
[DEBUG   ] Could not LazyLoad pkg.ex_mod_init
[INFO    ] Running state [MATE Desktop] at time 10:31:08.515147
[INFO    ] Executing state pkg.group_installed for MATE Desktop
[INFO    ] Executing command ['yum', '--quiet', 'groupinfo', 'MATE Desktop'] in directory '/root'
[INFO    ] Executing command ['yum', '--quiet', 'groupinfo', 'admin-tools'] in directory '/root'
[ERROR   ] An exception occurred in this state: Traceback (most recent call last):
File "/usr/lib/python2.7/site-packages/salt/state.py", line 1733, in call
**cdata['kwargs'])
File "/usr/lib/python2.7/site-packages/salt/loader.py", line 1651, in wrapper
return f(*args, **kwargs)
File "/usr/lib/python2.7/site-packages/salt/states/pkg.py", line 2231, in group_installed
diff = __salt__['pkg.group_diff'](name)
File "/usr/lib/python2.7/site-packages/salt/modules/yumpkg.py", line 1899, in group_diff
group_pkgs = group_info(name, expand=True)
File "/usr/lib/python2.7/site-packages/salt/modules/yumpkg.py", line 1863, in group_info
expanded = group_info(line, expand=True)
File "/usr/lib/python2.7/site-packages/salt/modules/yumpkg.py", line 1839, in group_info
raise CommandExecutionError('Group \'{0}\' not found'.format(name))
CommandExecutionError: Group 'admin-tools' not found

[INFO    ] Completed state [MATE Desktop] at time 10:31:14.453135 duration_in_ms=5937.988
[DEBUG   ] LazyLoaded highstate.output
local:
----------
ID: MATE Desktop
Function: pkg.group_installed
Result: False
Comment: An exception occurred in this state: Traceback (most recent call last):
File "/usr/lib/python2.7/site-packages/salt/state.py", line 1733, in call
**cdata['kwargs'])
File "/usr/lib/python2.7/site-packages/salt/loader.py", line 1651, in wrapper
return f(*args, **kwargs)
File "/usr/lib/python2.7/site-packages/salt/states/pkg.py", line 2231, in group_installed
diff = __salt__['pkg.group_diff'](name)
File "/usr/lib/python2.7/site-packages/salt/modules/yumpkg.py", line 1899, in group_diff
group_pkgs = group_info(name, expand=True)
File "/usr/lib/python2.7/site-packages/salt/modules/yumpkg.py", line 1863, in group_info
expanded = group_info(line, expand=True)
File "/usr/lib/python2.7/site-packages/salt/modules/yumpkg.py", line 1839, in group_info
raise CommandExecutionError('Group \'{0}\' not found'.format(name))
CommandExecutionError: Group 'admin-tools' not found
Started: 10:31:08.515147
Duration: 5937.988 ms
Changes:

# Fixed code
def group_installed(name, skip=None, include=None, **kwargs):
    """
    .. versionadded:: 2015.8.0

    Ensure that an entire package group is installed. This state is currently
    only supported for the :mod:`yum <salt.modules.yumpkg>` package manager.

    skip
        Packages that would normally be installed by the package group
        ("default" packages), which should not be installed.

        .. code-block:: yaml

            Load Balancer:
              pkg.group_installed:
                - skip:
                  - piranha

    include
        Packages which are included in a group, which would not normally be
        installed by a ``yum groupinstall`` ("optional" packages). Note that
        this will not enforce group membership; if you include packages which
        are not members of the specified groups, they will still be installed.

        .. code-block:: yaml

            Load Balancer:
              pkg.group_installed:
                - include:
                  - haproxy

        .. versionchanged:: 2016.3.0
            This option can no longer be passed as a comma-separated list, it
            must now be passed as a list (as shown in the above example).

    .. note::
        Because this is essentially a wrapper around :py:func:`pkg.install
        <salt.modules.yumpkg.install>`, any argument which can be passed to
        pkg.install may also be included here, and it will be passed on to the
        call to :py:func:`pkg.install <salt.modules.yumpkg.install>`.
    """
    ret = {"name": name, "changes": {}, "result": False, "comment": ""}

    if "pkg.group_diff" not in __salt__:
        ret["comment"] = "pkg.group_install not available for this platform"
        return ret

    if skip is None:
        skip = []
    else:
        if not isinstance(skip, list):
            ret["comment"] = "skip must be formatted as a list"
            return ret
        for idx, item in enumerate(skip):
            if not isinstance(item, six.string_types):
                skip[idx] = str(item)

    if include is None:
        include = []
    else:
        if not isinstance(include, list):
            ret["comment"] = "include must be formatted as a list"
            return ret
        for idx, item in enumerate(include):
            if not isinstance(item, six.string_types):
                include[idx] = str(item)

    try:
        diff = __salt__["pkg.group_diff"](name)
    except CommandExecutionError as err:
        ret["comment"] = (
            "An error was encountered while installing/updating "
            "group '{0}': {1}.".format(name, err)
        )
        return ret

    mandatory = diff["mandatory"]["installed"] + diff["mandatory"]["not installed"]

    invalid_skip = [x for x in mandatory if x in skip]
    if invalid_skip:
        ret["comment"] = (
            "The following mandatory packages cannot be skipped: {0}".format(
                ", ".join(invalid_skip)
            )
        )
        return ret

    targets = diff["mandatory"]["not installed"]
    targets.extend([x for x in diff["default"]["not installed"] if x not in skip])
    targets.extend(include)

    if not targets:
        ret["result"] = True
        ret["comment"] = "Group '{0}' is already installed".format(name)
        return ret

    partially_installed = (
        diff["mandatory"]["installed"]
        or diff["default"]["installed"]
        or diff["optional"]["installed"]
    )

    if __opts__["test"]:
        ret["result"] = None
        if partially_installed:
            ret["comment"] = (
                "Group '{0}' is partially installed and will be updated".format(name)
            )
        else:
            ret["comment"] = "Group '{0}' will be installed".format(name)
        return ret

    try:
        ret["changes"] = __salt__["pkg.install"](pkgs=targets, **kwargs)
    except CommandExecutionError as exc:
        ret = {"name": name, "result": False}
        if exc.info:
            # Get information for state return from the exception.
            ret["changes"] = exc.info.get("changes", {})
            ret["comment"] = exc.strerror_without_changes
        else:
            ret["changes"] = {}
            ret["comment"] = (
                "An error was encountered while "
                "installing/updating group '{0}': {1}".format(name, exc)
            )
        return ret

    failed = [x for x in targets if x not in __salt__["pkg.list_pkgs"]()]
    if failed:
        ret["comment"] = "Failed to install the following packages: {0}".format(
            ", ".join(failed)
        )
        return ret

    ret["result"] = True
    ret["comment"] = "Group '{0}' was {1}".format(
        name, "updated" if partially_installed else "installed"
    )
    return ret