Repository: ansible/ansible
Pull request: icinga2_feature: fix module in check_mode
##### SUMMARY
Fixes: #39830

Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>

##### ISSUE TYPE
 - Bugfix Pull Request

##### COMPONENT NAME
lib/ansible/modules/monitoring/icinga2_feature.py
test/units/modules/monitoring/test_icinga2_feature.py

##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes below -->
```
2.6devel
```

Problem statement:
Module `icinga2_feature` broken in `check_mode`
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
icinga2_feature

##### ANSIBLE VERSION
```
ansible 2.4.4.0
  config file = /home/ansible/ansible/ansible.cfg
  configured module search path = [u'/home/ansible/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python2.7/dist-packages/ansible
  executable location = /usr/local/bin/ansible
  python version = 2.7.13 (default, Nov 24 2017, 17:33:09) [GCC 6.3.0 20170516]
```

##### CONFIGURATION
ALLOW_WORLD_READABLE_TMPFILES(/home/ansible/ansible/ansible.cfg) = True
ANSIBLE_PIPELINING(/home/ansible/ansible/ansible.cfg) = True
CACHE_PLUGIN(/home/ansible/ansible/ansible.cfg) = jsonfile
CACHE_PLUGIN_CONNECTION(/home/ansible/ansible/ansible.cfg) = ~/.cache/
CACHE_PLUGIN_TIMEOUT(/home/ansible/ansible/ansible.cfg) = 86400
DEFAULT_BECOME(/home/ansible/ansible/ansible.cfg) = True
DEFAULT_FORKS(/home/ansible/ansible/ansible.cfg) = 10
DEFAULT_GATHERING(/home/ansible/ansible/ansible.cfg) = smart
DEFAULT_HOST_LIST(/home/ansible/ansible/ansible.cfg) = [u'/home/ansible/ansible/hosts']
DEFAULT_MANAGED_STR(/home/ansible/ansible/ansible.cfg) = Ansible managed file, do not edit directly
DEFAULT_REMOTE_USER(/home/ansible/ansible/ansible.cfg) = ansible
DEFAULT_ROLES_PATH(/home/ansible/ansible/ansible.cfg) = [u'/home/ansible/ansible/roles']
DEFAULT_VAULT_PASSWORD_FILE(/home/ansible/ansible/ansible.cfg) = /home/ansible/ansible/open_the_vault.sh
RETRY_FILES_ENABLED(/home/ansible/ansible/ansible.cfg) = False

##### OS / ENVIRONMENT
Debian Stretch GNU Linux

##### SUMMARY
The Ansible module `icinga2_feature` is broken when run in `check_mode`.

##### STEPS TO REPRODUCE
<!--- For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used. -->

<!--- Paste example playbooks or commands between quotes below -->
```yaml
- name: Enable Icinga2 features
  icinga2_feature:
    name: "{{ item }}"
    state: present
  with_items:
    - api
    - command
    - ido-mysql
  notify: restart icinga2
```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
The module should succeed in `check_mode`.

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes below -->
```
TASK [icinga2-server : Enable Icinga2 features] *******************************************
task path: /home/ansible/ansible/roles/icinga2-server/tasks/icinga2.yml:32
Using module file /usr/local/lib/python2.7/dist-packages/ansible/modules/monitoring/icinga2_feature.py
<monitor.example.org> ESTABLISH SSH CONNECTION FOR USER: ansible
<monitor.example.org> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=ansible -o ConnectTimeout=10 -o ControlPath=/home/ansible/.ansible/cp/6547af9212 monitor.example.org '/bin/sh -c '"'"'sudo -H -S -n -u root /bin/sh -c '"'"'"'"'"'"'"'"'echo BECOME-SUCCESS-bedifijcwlsgibsuolciioaayzpjnvhp; /usr/bin/python'"'"'"'"'"'"'"'"' && sleep 0'"'"''
<monitor.example.org> (1, '', 'Traceback (most recent call last):\n  File "/tmp/ansible_i6MMOX/ansible_module_icinga2_feature.py", line 127, in <module>\n    main()\n  File "/tmp/ansible_i6MMOX/ansible_module_icinga2_feature.py", line 124, in main\n    Icinga2FeatureHelper(module).manage()\n  File "/tmp/ansible_i6MMOX/ansible_module_icinga2_feature.py", line 76, in manage\n    if re.search("Disabled features:.* %s[ \\n]" % self.module.params["name"], out) \\\n  File "/usr/lib/python2.7/re.py", line 146, in search\n    return _compile(pattern, flags).search(string)\nTypeError: expected string or buffer\n')
The full traceback is:
Traceback (most recent call last):
  File "/tmp/ansible_i6MMOX/ansible_module_icinga2_feature.py", line 127, in <module>
    main()
  File "/tmp/ansible_i6MMOX/ansible_module_icinga2_feature.py", line 124, in main
    Icinga2FeatureHelper(module).manage()
  File "/tmp/ansible_i6MMOX/ansible_module_icinga2_feature.py", line 76, in manage
    if re.search("Disabled features:.* %s[ \n]" % self.module.params["name"], out) \
  File "/usr/lib/python2.7/re.py", line 146, in search
    return _compile(pattern, flags).search(string)
TypeError: expected string or buffer

failed: [monitor.example.org] (item=api) => {
    "changed": false, 
    "failed": true, 
    "item": "api", 
    "module_stderr": "Traceback (most recent call last):\n  File \"/tmp/ansible_i6MMOX/ansible_module_icinga2_feature.py\", line 127, in <module>\n    main()\n  File \"/tmp/ansible_i6MMOX/ansible_module_icinga2_feature.py\", line 124, in main\n    Icinga2FeatureHelper(module).manage()\n  File \"/tmp/ansible_i6MMOX/ansible_module_icinga2_feature.py\", line 76, in manage\n    if re.search(\"Disabled features:.* %s[ \\n]\" % self.module.params[\"name\"], out) \\\n  File \"/usr/lib/python2.7/re.py\", line 146, in search\n    return _compile(pattern, flags).search(string)\nTypeError: expected string or buffer\n", 
    "module_stdout": "", 
    "msg": "MODULE FAILURE", 
    "rc": 1
}
Using module file /usr/local/lib/python2.7/dist-packages/ansible/modules/monitoring/icinga2_feature.py
<monitor.example.org> ESTABLISH SSH CONNECTION FOR USER: ansible
<monitor.example.org> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=ansible -o ConnectTimeout=10 -o ControlPath=/home/ansible/.ansible/cp/6547af9212 monitor.example.org '/bin/sh -c '"'"'sudo -H -S -n -u root /bin/sh -c '"'"'"'"'"'"'"'"'echo BECOME-SUCCESS-hdfcawsogmmfrhwhmcfyzjciphyotqqw; /usr/bin/python'"'"'"'"'"'"'"'"' && sleep 0'"'"''
<monitor.example.org> (1, '', 'Traceback (most recent call last):\n  File "/tmp/ansible_yR2VVe/ansible_module_icinga2_feature.py", line 127, in <module>\n    main()\n  File "/tmp/ansible_yR2VVe/ansible_module_icinga2_feature.py", line 124, in main\n    Icinga2FeatureHelper(module).manage()\n  File "/tmp/ansible_yR2VVe/ansible_module_icinga2_feature.py", line 76, in manage\n    if re.search("Disabled features:.* %s[ \\n]" % self.module.params["name"], out) \\\n  File "/usr/lib/python2.7/re.py", line 146, in search\n    return _compile(pattern, flags).search(string)\nTypeError: expected string or buffer\n')
The full traceback is:
Traceback (most recent call last):
  File "/tmp/ansible_yR2VVe/ansible_module_icinga2_feature.py", line 127, in <module>
    main()
  File "/tmp/ansible_yR2VVe/ansible_module_icinga2_feature.py", line 124, in main
    Icinga2FeatureHelper(module).manage()
  File "/tmp/ansible_yR2VVe/ansible_module_icinga2_feature.py", line 76, in manage
    if re.search("Disabled features:.* %s[ \n]" % self.module.params["name"], out) \
  File "/usr/lib/python2.7/re.py", line 146, in search
    return _compile(pattern, flags).search(string)
TypeError: expected string or buffer

failed: [monitor.example.org] (item=command) => {
    "changed": false, 
    "failed": true, 
    "item": "command", 
    "module_stderr": "Traceback (most recent call last):\n  File \"/tmp/ansible_yR2VVe/ansible_module_icinga2_feature.py\", line 127, in <module>\n    main()\n  File \"/tmp/ansible_yR2VVe/ansible_module_icinga2_feature.py\", line 124, in main\n    Icinga2FeatureHelper(module).manage()\n  File \"/tmp/ansible_yR2VVe/ansible_module_icinga2_feature.py\", line 76, in manage\n    if re.search(\"Disabled features:.* %s[ \\n]\" % self.module.params[\"name\"], out) \\\n  File \"/usr/lib/python2.7/re.py\", line 146, in search\n    return _compile(pattern, flags).search(string)\nTypeError: expected string or buffer\n", 
    "module_stdout": "", 
    "msg": "MODULE FAILURE", 
    "rc": 1
}
Using module file /usr/local/lib/python2.7/dist-packages/ansible/modules/monitoring/icinga2_feature.py
<monitor.example.org> ESTABLISH SSH CONNECTION FOR USER: ansible
<monitor.example.org> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=ansible -o ConnectTimeout=10 -o ControlPath=/home/ansible/.ansible/cp/6547af9212 monitor.example.org '/bin/sh -c '"'"'sudo -H -S -n -u root /bin/sh -c '"'"'"'"'"'"'"'"'echo BECOME-SUCCESS-bkbraoemgssmthimioxrkyoqcjxfgrhm; /usr/bin/python'"'"'"'"'"'"'"'"' && sleep 0'"'"''
<monitor.example.org> (1, '', 'Traceback (most recent call last):\n  File "/tmp/ansible__gk_ri/ansible_module_icinga2_feature.py", line 127, in <module>\n    main()\n  File "/tmp/ansible__gk_ri/ansible_module_icinga2_feature.py", line 124, in main\n    Icinga2FeatureHelper(module).manage()\n  File "/tmp/ansible__gk_ri/ansible_module_icinga2_feature.py", line 76, in manage\n    if re.search("Disabled features:.* %s[ \\n]" % self.module.params["name"], out) \\\n  File "/usr/lib/python2.7/re.py", line 146, in search\n    return _compile(pattern, flags).search(string)\nTypeError: expected string or buffer\n')
The full traceback is:
Traceback (most recent call last):
  File "/tmp/ansible__gk_ri/ansible_module_icinga2_feature.py", line 127, in <module>
    main()
  File "/tmp/ansible__gk_ri/ansible_module_icinga2_feature.py", line 124, in main
    Icinga2FeatureHelper(module).manage()
  File "/tmp/ansible__gk_ri/ansible_module_icinga2_feature.py", line 76, in manage
    if re.search("Disabled features:.* %s[ \n]" % self.module.params["name"], out) \
  File "/usr/lib/python2.7/re.py", line 146, in search
    return _compile(pattern, flags).search(string)
TypeError: expected string or buffer

failed: [monitor.example.org] (item=ido-mysql) => {
    "changed": false, 
    "failed": true, 
    "item": "ido-mysql", 
    "module_stderr": "Traceback (most recent call last):\n  File \"/tmp/ansible__gk_ri/ansible_module_icinga2_feature.py\", line 127, in <module>\n    main()\n  File \"/tmp/ansible__gk_ri/ansible_module_icinga2_feature.py\", line 124, in main\n    Icinga2FeatureHelper(module).manage()\n  File \"/tmp/ansible__gk_ri/ansible_module_icinga2_feature.py\", line 76, in manage\n    if re.search(\"Disabled features:.* %s[ \\n]\" % self.module.params[\"name\"], out) \\\n  File \"/usr/lib/python2.7/re.py\", line 146, in search\n    return _compile(pattern, flags).search(string)\nTypeError: expected string or buffer\n", 
    "module_stdout": "", 
    "msg": "MODULE FAILURE", 
    "rc": 1
}

```

Hints from issue discussion:
Files identified in the description:
* [lib/ansible/modules/monitoring/icinga2_feature.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/monitoring/icinga2_feature.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @nerzhul
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Commit message: icinga2_feature: fix module in check_mode

Fixes: #39830

Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>

Patch to review:
diff --git a/lib/ansible/modules/monitoring/icinga2_feature.py b/lib/ansible/modules/monitoring/icinga2_feature.py
index c85af1b1088071..da6304582faedb 100644
--- a/lib/ansible/modules/monitoring/icinga2_feature.py
+++ b/lib/ansible/modules/monitoring/icinga2_feature.py
@@ -1,18 +1,22 @@
 #!/usr/bin/python
 # -*- coding: utf-8 -*-
 
-# (c) 2016, Loic Blot <loic.blot@unix-experience.fr>
+# Copyright (c) 2016, Loic Blot <loic.blot@unix-experience.fr>
+# Copyright (c) 2018, Ansible Project
 # Sponsored by Infopro Digital. http://www.infopro-digital.com/
 # Sponsored by E.T.A.I. http://www.etai.fr/
+#
 # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
 
 from __future__ import absolute_import, division, print_function
 __metaclass__ = type
 
 
-ANSIBLE_METADATA = {'metadata_version': '1.1',
-                    'status': ['preview'],
-                    'supported_by': 'community'}
+ANSIBLE_METADATA = {
+    'metadata_version': '1.1',
+    'status': ['preview'],
+    'supported_by': 'community'
+}
 
 
 DOCUMENTATION = '''
@@ -21,20 +25,22 @@
 
 short_description: Manage Icinga2 feature
 description:
-    - Enable or disable an Icinga2 feature
+    - This module can be used to enable or disable an Icinga2 feature.
 version_added: "2.3"
 author: "Loic Blot (@nerzhul)"
 options:
     name:
-        description:
-            - This is the feature name to enable or disable.
-        required: True
+      description:
+      - This is the feature name to enable or disable.
+      required: True
     state:
-        description:
-            - Apply feature state.
-        required: false
-        choices: [ "present", "absent" ]
-        default: present
+      description:
+      - If set to C(present) and feature is disabled, then feature is enabled.
+      - If set to C(present) and feature is already enabled, then nothing is changed.
+      - If set to C(absent) and feature is enabled, then feature is disabled.
+      - If set to C(absent) and feature is already disabled, then nothing is changed.
+      choices: [ "present", "absent" ]
+      default: present
 '''
 
 EXAMPLES = '''
@@ -42,6 +48,11 @@
   icinga2_feature:
     name: ido-pgsql
     state: present
+
+- name: Disable api feature
+  icinga2_feature:
+    name: api
+    state: absent
 '''
 
 RETURN = '''
@@ -49,7 +60,6 @@
 '''
 
 import re
-
 from ansible.module_utils.basic import AnsibleModule
 
 
@@ -57,13 +67,15 @@ class Icinga2FeatureHelper:
     def __init__(self, module):
         self.module = module
         self._icinga2 = module.get_bin_path('icinga2', True)
+        self.feature_name = self.module.params['name']
+        self.state = self.module.params['state']
 
     def _exec(self, args):
         if not self.module.check_mode:
             cmd = [self._icinga2, 'feature']
             rc, out, err = self.module.run_command(cmd + args, check_rc=True)
             return rc, out
-        return 0, list()
+        return 0, ""
 
     def manage(self):
         rc, out = self._exec(["list"])
@@ -73,26 +85,29 @@ def manage(self):
 
         else:
             # If feature is already in good state, just exit
-            if re.search("Disabled features:.* %s[ \n]" % self.module.params["name"], out) \
-                    and self.module.params["state"] == "absent" or \
-                    re.search("Enabled features:.* %s[ \n]" % self.module.params["name"], out) \
-                    and self.module.params["state"] == "present":
+            if re.search("Disabled features:.* %s[ \n]" % self.feature_name, out) \
+                    and self.state == "absent" or \
+                    re.search("Enabled features:.* %s[ \n]" % self.feature_name, out) \
+                    and self.state == "present":
                 self.module.exit_json(changed=False)
 
             if self.module.check_mode:
                 self.module.exit_json(changed=True)
 
-        if self.module.params["state"] == "present":
+        if self.state == "present":
             feature_enable_str = "enable"
         else:
             feature_enable_str = "disable"
 
-        rc, out = self._exec([feature_enable_str, self.module.params["name"]])
+        rc, out = self._exec([feature_enable_str, self.feature_name])
 
-        if self.module.params["state"] == "present":
+        change_applied = False
+        if self.state == "present":
             if rc != 0:
-                self.module.fail_json(msg="Fail to %s feature %s. icinga2 command returned %s"
-                                      % (feature_enable_str, self.module.params["name"], out))
+                self.module.fail_json(msg="Failed to %s feature %s."
+                                          " icinga2 command returned %s" % (feature_enable_str,
+                                                                            self.feature_name,
+                                                                            out))
 
             if re.search("already enabled", out) is None:
                 change_applied = True
@@ -102,11 +117,10 @@ def manage(self):
             if rc == 0:
                 change_applied = True
             # RC is not 0 for this already disabled feature, handle it as no change applied
-            elif re.search("Cannot disable feature '%s'. Target file .* does not exist"
-                           % self.module.params["name"]):
+            elif re.search("Cannot disable feature '%s'. Target file .* does not exist" % self.feature_name, out):
                 change_applied = False
             else:
-                self.module.fail_json(msg="Fail to disable feature. Command returns %s" % out)
+                self.module.fail_json(msg="Failed to disable feature. Command returns %s" % out)
 
         self.module.exit_json(changed=change_applied)
 
@@ -114,8 +128,8 @@ def manage(self):
 def main():
     module = AnsibleModule(
         argument_spec=dict(
-            name=dict(required=True, type='str'),
-            state=dict(required=False, type='str', choices=["present", "absent"], default="present")
+            name=dict(type='str', required=True),
+            state=dict(type='str', choices=["present", "absent"], default="present")
         ),
         supports_check_mode=True
     )
@@ -123,5 +137,6 @@ def main():
     module.run_command_environ_update = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C', LC_CTYPE='C')
     Icinga2FeatureHelper(module).manage()
 
+
 if __name__ == '__main__':
     main()
diff --git a/test/units/modules/monitoring/test_icinga2_feature.py b/test/units/modules/monitoring/test_icinga2_feature.py
new file mode 100644
index 00000000000000..69cf5b1d3860d2
--- /dev/null
+++ b/test/units/modules/monitoring/test_icinga2_feature.py
@@ -0,0 +1,96 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2018, Ansible Project
+# Copyright (c) 2018, Abhijeet Kasurde <akasurde@redhat.com>
+#
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from ansible.modules.monitoring import icinga2_feature
+from units.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args
+from ansible.compat.tests.mock import patch
+from ansible.module_utils import basic
+
+
+def get_bin_path(*args, **kwargs):
+    """Function to return path of icinga2 binary."""
+    return "/bin/icinga2"
+
+
+class TestIcinga2Feature(ModuleTestCase):
+    """Main class for testing icinga2_feature module."""
+
+    def setUp(self):
+        """Setup."""
+        super(TestIcinga2Feature, self).setUp()
+        self.module = icinga2_feature
+        self.mock_get_bin_path = patch.object(basic.AnsibleModule, 'get_bin_path', get_bin_path)
+        self.mock_get_bin_path.start()
+        self.addCleanup(self.mock_get_bin_path.stop)  # ensure that the patching is 'undone'
+
+    def tearDown(self):
+        """Teardown."""
+        super(TestIcinga2Feature, self).tearDown()
+
+    def test_without_required_parameters(self):
+        """Failure must occurs when all parameters are missing."""
+        with self.assertRaises(AnsibleFailJson):
+            set_module_args({})
+            self.module.main()
+
+    def test_enable_feature(self):
+        """Check that result is changed."""
+        set_module_args({
+            'name': 'api',
+        })
+        with patch.object(basic.AnsibleModule, 'run_command') as run_command:
+            run_command.return_value = 0, '', ''  # successful execution, no output
+            with self.assertRaises(AnsibleExitJson) as result:
+                icinga2_feature.main()
+                self.assertTrue(result.exception.args[0]['changed'])
+
+        self.assertEqual(run_command.call_count, 2)
+        self.assertEqual(run_command.call_args[0][0][-1], 'api')
+
+    def test_enable_feature_with_check_mode(self):
+        """Check that result is changed in check mode."""
+        set_module_args({
+            'name': 'api',
+            '_ansible_check_mode': True,
+        })
+        with patch.object(basic.AnsibleModule, 'run_command') as run_command:
+            run_command.return_value = 0, '', ''  # successful execution, no output
+            with self.assertRaises(AnsibleExitJson) as result:
+                icinga2_feature.main()
+                self.assertTrue(result.exception.args[0]['changed'])
+
+        self.assertEqual(run_command.call_count, 0)
+
+    def test_disable_feature(self):
+        """Check that result is changed."""
+        set_module_args({
+            'name': 'api',
+            'state': 'absent'
+        })
+        with patch.object(basic.AnsibleModule, 'run_command') as run_command:
+            run_command.return_value = 0, '', ''  # successful execution, no output
+            with self.assertRaises(AnsibleExitJson) as result:
+                icinga2_feature.main()
+                self.assertTrue(result.exception.args[0]['changed'])
+
+        self.assertEqual(run_command.call_count, 2)
+        self.assertEqual(run_command.call_args[0][0][-1], 'api')
+
+    def test_disable_feature_with_check_mode(self):
+        """Check that result is changed in check mode."""
+        set_module_args({
+            'name': 'api',
+            'state': 'absent',
+            '_ansible_check_mode': True,
+        })
+        with patch.object(basic.AnsibleModule, 'run_command') as run_command:
+            run_command.return_value = 0, '', ''  # successful execution, no output
+            with self.assertRaises(AnsibleExitJson) as result:
+                icinga2_feature.main()
+                self.assertTrue(result.exception.args[0]['changed'])
+
+        self.assertEqual(run_command.call_count, 0)


Code review comments:
diff_hunk: @@ -21,49 +25,57 @@
 
 short_description: Manage Icinga2 feature
 description:
-    - Enable or disable an Icinga2 feature
+    - This module can be used to enable or disable an Icinga2 feature.
 version_added: "2.3"
 author: "Loic Blot (@nerzhul)"
 options:
     name:
-        description:
-            - This is the feature name to enable or disable.
-        required: True
+      description:
+      - This is the feature name to enable or disable.
+      required: True
     state:
-        description:
-            - Apply feature state.
-        required: false
-        choices: [ "present", "absent" ]
-        default: present
+      description:
+      - If set to C(present) and feature is disabled, then feature is enabled.
+      - If set to C(present) and feature is already enabled, then nothing is changed.
+      - If set to C(absent) and feature is enabled, then feature is disabled.
+      - If set to C(absent) and feature is already disabled, then nothing is changed.
+      choices: [ "present", "absent" ]
+      default: present
 '''
 
 EXAMPLES = '''
 - name: Enable ido-pgsql feature
   icinga2_feature:
     name: ido-pgsql
     state: present
+
+- name: Disable api feature
+  icinga2_feature:
+    name: api
+    state: absent
 '''
 
 RETURN = '''
 #
 '''
 
 import re
-
 from ansible.module_utils.basic import AnsibleModule
 
 
 class Icinga2FeatureHelper:
     def __init__(self, module):
         self.module = module
         self._icinga2 = module.get_bin_path('icinga2', True)
+        self.feature_name = self.module.params['name']
+        self.state = self.module.params['state']
 
     def _exec(self, args):
         if not self.module.check_mode:
path: lib/ansible/modules/monitoring/icinga2_feature.py
text: @user1:
This condition is the problem. It leads to `icinga2 feature list` never beeing executed in `check_mode`, which results in empty `out`. The condition should be removed alltogether. `self._exec` is only invoked twice anyway: first to run `icinga2 feature list` (should be done regardless of `check_mode` or not) and second at a place in code that is never reached in `check_mode` anyway.