# Buggy code
def ensure(module, base, state, names, autoremove):
    # Accumulate failures.  Package management modules install what they can
    # and fail with a message about what they can't.
    failures = []
    allow_erasing = False

    # Autoremove is called alone
    # Jump to remove path where base.autoremove() is run
    if not names and autoremove is not None:
        names = []
        state = "absent"

    if names == ["*"] and state == "latest":
        base.upgrade_all()
    else:
        pkg_specs, group_specs, filenames = _parse_spec_group_file(names)
        if group_specs:
            base.read_comps()

        pkg_specs = [p.strip() for p in pkg_specs]
        filenames = [f.strip() for f in filenames]
        groups = []
        environments = []
        for group_spec in (g.strip() for g in group_specs):
            group = base.comps.group_by_pattern(group_spec)
            if group:
                groups.append(group)
            else:
                environment = base.comps.environment_by_pattern(group_spec)
                if environment:
                    environments.append(environment.id)
                else:
                    module.fail_json(msg="No group {0} available.".format(group_spec))

        if state in ["installed", "present"]:
            # Install files.
            _install_remote_rpms(base, filenames)

            # Install groups.
            for group in groups:
                try:
                    base.group_install(group.id, dnf.const.GROUP_PACKAGE_TYPES)
                except dnf.exceptions.Error as e:
                    # In dnf 2.0 if all the mandatory packages in a group do
                    # not install, an error is raised.  We want to capture
                    # this but still install as much as possible.
                    failures.append((group, e))

            for environment in environments:
                try:
                    base.environment_install(environment, dnf.const.GROUP_PACKAGE_TYPES)
                except dnf.exceptions.Error as e:
                    failures.append((group, e))

            # Install packages.
            for pkg_spec in pkg_specs:
                _mark_package_install(module, base, pkg_spec)

        elif state == "latest":
            # "latest" is same as "installed" for filenames.
            _install_remote_rpms(base, filenames)

            for group in groups:
                try:
                    try:
                        base.group_upgrade(group)
                    except dnf.exceptions.CompsError:
                        # If not already installed, try to install.
                        base.group_install(group.id, dnf.const.GROUP_PACKAGE_TYPES)
                except dnf.exceptions.Error as e:
                    failures.append((group, e))

            for environment in environments:
                try:
                    try:
                        base.environment_upgrade(environment)
                    except dnf.exceptions.CompsError:
                        # If not already installed, try to install.
                        base.environment_install(group, dnf.const.GROUP_PACKAGE_TYPES)
                except dnf.exceptions.Error as e:
                    failures.append((group, e))

            for pkg_spec in pkg_specs:
                # best effort causes to install the latest package
                # even if not previously installed
                base.conf.best = True
                base.install(pkg_spec)

        else:
            # state == absent
            if autoremove is not None:
                base.conf.clean_requirements_on_remove = autoremove

            if filenames:
                module.fail_json(
                    msg="Cannot remove paths -- please specify package name."
                )

            for group in groups:
                try:
                    base.group_remove(group)
                except dnf.exceptions.CompsError:
                    # Group is already uninstalled.
                    pass

            for environment in environments:
                try:
                    base.environment_remove(environment)
                except dnf.exceptions.CompsError:
                    # Environment is already uninstalled.
                    pass

            installed = base.sack.query().installed()
            for pkg_spec in pkg_specs:
                if installed.filter(name=pkg_spec):
                    base.remove(pkg_spec)

            # Like the dnf CLI we want to allow recursive removal of dependent
            # packages
            allow_erasing = True

            if autoremove:
                base.autoremove()

    if not base.resolve(allow_erasing=allow_erasing):
        if failures:
            module.fail_json(
                msg="Failed to install some of the specified packages",
                failures=failures,
            )
        module.exit_json(msg="Nothing to do")
    else:
        if module.check_mode:
            if failures:
                module.fail_json(
                    msg="Failed to install some of the specified packages",
                    failures=failures,
                )
            module.exit_json(changed=True)

        base.download_packages(base.transaction.install_set)
        base.do_transaction()
        response = {"changed": True, "results": []}
        for package in base.transaction.install_set:
            response["results"].append("Installed: {0}".format(package))
        for package in base.transaction.remove_set:
            response["results"].append("Removed: {0}".format(package))

        if failures:
            module.fail_json(
                msg="Failed to install some of the specified packages",
                failures=failures,
            )
        module.exit_json(**response)


# Traceback (ValueError)
ansible 2.4.0 (devel 2fc1df7870) last updated 2017/07/16 09:51:32 (GMT +200)
config file = /etc/ansible/ansible.cfg
configured module search path = [u'/home/richi/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
ansible python module location = /home/richi/ansible/lib/ansible
executable location = /home/richi/ansible/bin/ansible
python version = 2.7.13 (default, Jun 26 2017, 10:20:05) [GCC 7.1.1 20170622 (Red Hat 7.1.1-3)]
Using /etc/ansible/ansible.cfg as config file
Parsed /etc/ansible/hosts inventory source with ini plugin
META: ran handlers
Using module_utils file /home/richi/ansible/lib/ansible/module_utils/basic.py
Using module_utils file /home/richi/ansible/lib/ansible/module_utils/six/__init__.py
Using module_utils file /home/richi/ansible/lib/ansible/module_utils/_text.py
Using module_utils file /home/richi/ansible/lib/ansible/module_utils/pycompat24.py
Using module_utils file /home/richi/ansible/lib/ansible/module_utils/six/_six.py
Using module file /home/richi/ansible/lib/ansible/modules/packaging/os/dnf.py
<127.0.0.1> ESTABLISH LOCAL CONNECTION FOR USER: richi
<127.0.0.1> EXEC /bin/sh -c 'echo ~ &amp;&amp; sleep 0'
<127.0.0.1> EXEC /bin/sh -c '( umask 77 &amp;&amp; mkdir -p "` echo /home/richi/.ansible/tmp/ansible-tmp-1500193317.57-197233066500256 `" &amp;&amp; echo ansible-tmp-1500193317.57-197233066500256="` echo /home/richi/.ansible/tmp/ansible-tmp-1500193317.57-197233066500256 `" ) &amp;&amp; sleep 0'
<127.0.0.1> PUT /tmp/tmpvAhkzH TO /home/richi/.ansible/tmp/ansible-tmp-1500193317.57-197233066500256/dnf.py
<127.0.0.1> EXEC /bin/sh -c 'chmod u+x /home/richi/.ansible/tmp/ansible-tmp-1500193317.57-197233066500256/ /home/richi/.ansible/tmp/ansible-tmp-1500193317.57-197233066500256/dnf.py &amp;&amp; sleep 0'
<127.0.0.1> EXEC /bin/sh -c 'sudo -H -S  -p "[sudo via ansible, key=nbziqchaxlxwhfadmpewhgkhyxlpkbel] password: " -u root /bin/sh -c '"'"'echo BECOME-SUCCESS-nbziqchaxlxwhfadmpewhgkhyxlpkbel; /usr/bin/python /home/richi/.ansible/tmp/ansible-tmp-1500193317.57-197233066500256/dnf.py; rm -rf "/home/richi/.ansible/tmp/ansible-tmp-1500193317.57-197233066500256/" > /dev/null 2>&amp;1'"'"' &amp;&amp; sleep 0'
The full traceback is:
Traceback (most recent call last):
File "/tmp/ansible_EsBR_K/ansible_module_dnf.py", line 543, in <module>
main()
File "/tmp/ansible_EsBR_K/ansible_module_dnf.py", line 539, in main
ensure(module, base, params['state'], params['name'], params['autoremove'])
File "/tmp/ansible_EsBR_K/ansible_module_dnf.py", line 378, in ensure
base.group_install(group, dnf.const.GROUP_PACKAGE_TYPES)
File "/usr/lib/python2.7/site-packages/dnf/base.py", line 1460, in group_install
strict)
File "/usr/lib/python2.7/site-packages/dnf/comps.py", line 90, in install_or_skip
return install_fnc(grp_or_env_id, types, exclude, strict)
File "/usr/lib/python2.7/site-packages/dnf/comps.py", line 600, in _group_install
raise ValueError(_("Group_id '%s' does not exist.") % ucd(group_id))
ValueError: Group_id '<dnf.comps.Group object at 0x7f75df2b26d0>' does not exist.
localhost | FAILED! => {
"changed": false,
"failed": true,
"module_stderr": "Traceback (most recent call last):\n  File \"/tmp/ansible_EsBR_K/ansible_module_dnf.py\", line 543, in <module>\n    main()\n  File \"/tmp/ansible_EsBR_K/ansible_module_dnf.py\", line 539, in main\n    ensure(module, base, params['state'], params['name'], params['autoremove'])\n  File \"/tmp/ansible_EsBR_K/ansible_module_dnf.py\", line 378, in ensure\n    base.group_install(group, dnf.const.GROUP_PACKAGE_TYPES)\n  File \"/usr/lib/python2.7/site-packages/dnf/base.py\", line 1460, in group_install\n    strict)\n  File \"/usr/lib/python2.7/site-packages/dnf/comps.py\", line 90, in install_or_skip\n    return install_fnc(grp_or_env_id, types, exclude, strict)\n  File \"/usr/lib/python2.7/site-packages/dnf/comps.py\", line 600, in _group_install\n    raise ValueError(_(\"Group_id '%s' does not exist.\") % ucd(group_id))\nValueError: Group_id '<dnf.comps.Group object at 0x7f75df2b26d0>' does not exist.\n",
"module_stdout": "",
"msg": "MODULE FAILURE",
"rc": 0
}

# Fixed code
def ensure(module, base, state, names, autoremove):
    # Accumulate failures.  Package management modules install what they can
    # and fail with a message about what they can't.
    failures = []
    allow_erasing = False

    # Autoremove is called alone
    # Jump to remove path where base.autoremove() is run
    if not names and autoremove is not None:
        names = []
        state = "absent"

    if names == ["*"] and state == "latest":
        base.upgrade_all()
    else:
        pkg_specs, group_specs, filenames = _parse_spec_group_file(names)
        if group_specs:
            base.read_comps()

        pkg_specs = [p.strip() for p in pkg_specs]
        filenames = [f.strip() for f in filenames]
        groups = []
        environments = []
        for group_spec in (g.strip() for g in group_specs):
            group = base.comps.group_by_pattern(group_spec)
            if group:
                groups.append(group)
            else:
                environment = base.comps.environment_by_pattern(group_spec)
                if environment:
                    environments.append(environment.id)
                else:
                    module.fail_json(msg="No group {0} available.".format(group_spec))

        if state in ["installed", "present"]:
            # Install files.
            _install_remote_rpms(base, filenames)

            # Install groups.
            for group in groups:
                try:
                    base.group_install(group.id, dnf.const.GROUP_PACKAGE_TYPES)
                except dnf.exceptions.Error as e:
                    # In dnf 2.0 if all the mandatory packages in a group do
                    # not install, an error is raised.  We want to capture
                    # this but still install as much as possible.
                    failures.append((group, e))

            for environment in environments:
                try:
                    base.environment_install(environment, dnf.const.GROUP_PACKAGE_TYPES)
                except dnf.exceptions.Error as e:
                    failures.append((group, e))

            # Install packages.
            for pkg_spec in pkg_specs:
                _mark_package_install(module, base, pkg_spec)

        elif state == "latest":
            # "latest" is same as "installed" for filenames.
            _install_remote_rpms(base, filenames)

            for group in groups:
                try:
                    try:
                        base.group_upgrade(group.id)
                    except dnf.exceptions.CompsError:
                        # If not already installed, try to install.
                        base.group_install(group.id, dnf.const.GROUP_PACKAGE_TYPES)
                except dnf.exceptions.Error as e:
                    failures.append((group, e))

            for environment in environments:
                try:
                    try:
                        base.environment_upgrade(environment)
                    except dnf.exceptions.CompsError:
                        # If not already installed, try to install.
                        base.environment_install(group, dnf.const.GROUP_PACKAGE_TYPES)
                except dnf.exceptions.Error as e:
                    failures.append((group, e))

            for pkg_spec in pkg_specs:
                # best effort causes to install the latest package
                # even if not previously installed
                base.conf.best = True
                base.install(pkg_spec)

        else:
            # state == absent
            if autoremove is not None:
                base.conf.clean_requirements_on_remove = autoremove

            if filenames:
                module.fail_json(
                    msg="Cannot remove paths -- please specify package name."
                )

            for group in groups:
                try:
                    base.group_remove(group.id)
                except dnf.exceptions.CompsError:
                    # Group is already uninstalled.
                    pass

            for environment in environments:
                try:
                    base.environment_remove(environment)
                except dnf.exceptions.CompsError:
                    # Environment is already uninstalled.
                    pass

            installed = base.sack.query().installed()
            for pkg_spec in pkg_specs:
                if installed.filter(name=pkg_spec):
                    base.remove(pkg_spec)

            # Like the dnf CLI we want to allow recursive removal of dependent
            # packages
            allow_erasing = True

            if autoremove:
                base.autoremove()

    if not base.resolve(allow_erasing=allow_erasing):
        if failures:
            module.fail_json(
                msg="Failed to install some of the specified packages",
                failures=failures,
            )
        module.exit_json(msg="Nothing to do")
    else:
        if module.check_mode:
            if failures:
                module.fail_json(
                    msg="Failed to install some of the specified packages",
                    failures=failures,
                )
            module.exit_json(changed=True)

        base.download_packages(base.transaction.install_set)
        base.do_transaction()
        response = {"changed": True, "results": []}
        for package in base.transaction.install_set:
            response["results"].append("Installed: {0}".format(package))
        for package in base.transaction.remove_set:
            response["results"].append("Removed: {0}".format(package))

        if failures:
            module.fail_json(
                msg="Failed to install some of the specified packages",
                failures=failures,
            )
        module.exit_json(**response)