We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
icinga2_host - ip should not be required
### Summary

Hi all,
as one can see in https://icinga.com/docs/icinga-2/latest/doc/09-object-types/#host the address variable is not mandatory, so IP should be optional in the plugin, too.


### Issue Type

Bug Report

### Component Name

icinga2_host

### Ansible Version

```console (paste below)
$ ansible --version
2.11.4
```


### Community.general Version

```console (paste below)
$ ansible-galaxy collection list community.general
5.5.0
```


### Configuration

```console (paste below)

```


### OS / Environment

Ubuntu 22.04

### Steps to Reproduce
Try to create a host without given an IP


### Expected Results

Address is optionally

### Actual Results

Address is mandatory



### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `plugins/modules/icinga2_host.py`
Content:
```
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3 
4 # This module is proudly sponsored by CGI (www.cgi.com) and
5 # KPN (www.kpn.com).
6 # Copyright (c) Ansible project
7 # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
8 # SPDX-License-Identifier: GPL-3.0-or-later
9 
10 from __future__ import absolute_import, division, print_function
11 __metaclass__ = type
12 
13 
14 DOCUMENTATION = '''
15 ---
16 module: icinga2_host
17 short_description: Manage a host in Icinga2
18 description:
19    - "Add or remove a host to Icinga2 through the API."
20    - "See U(https://www.icinga.com/docs/icinga2/latest/doc/12-icinga2-api/)"
21 author: "Jurgen Brand (@t794104)"
22 attributes:
23   check_mode:
24     support: full
25   diff_mode:
26     support: none
27 options:
28   url:
29     type: str
30     description:
31       - HTTP, HTTPS, or FTP URL in the form (http|https|ftp)://[user[:pass]]@host.domain[:port]/path
32   use_proxy:
33     description:
34       - If V(false), it will not use a proxy, even if one is defined in
35         an environment variable on the target hosts.
36     type: bool
37     default: true
38   validate_certs:
39     description:
40       - If V(false), SSL certificates will not be validated. This should only be used
41         on personally controlled sites using self-signed certificates.
42     type: bool
43     default: true
44   url_username:
45     type: str
46     description:
47       - The username for use in HTTP basic authentication.
48       - This parameter can be used without O(url_password) for sites that allow empty passwords.
49   url_password:
50     type: str
51     description:
52         - The password for use in HTTP basic authentication.
53         - If the O(url_username) parameter is not specified, the O(url_password) parameter will not be used.
54   force_basic_auth:
55     description:
56       - httplib2, the library used by the uri module only sends authentication information when a webservice
57         responds to an initial request with a 401 status. Since some basic auth services do not properly
58         send a 401, logins will fail. This option forces the sending of the Basic authentication header
59         upon initial request.
60     type: bool
61     default: false
62   client_cert:
63     type: path
64     description:
65       - PEM formatted certificate chain file to be used for SSL client
66         authentication. This file can also include the key as well, and if
67         the key is included, O(client_key) is not required.
68   client_key:
69     type: path
70     description:
71       - PEM formatted file that contains your private key to be used for SSL
72         client authentication. If O(client_cert) contains both the certificate
73         and key, this option is not required.
74   state:
75     type: str
76     description:
77       - Apply feature state.
78     choices: [ "present", "absent" ]
79     default: present
80   name:
81     type: str
82     description:
83       - Name used to create / delete the host. This does not need to be the FQDN, but does needs to be unique.
84     required: true
85     aliases: [host]
86   zone:
87     type: str
88     description:
89       - The zone from where this host should be polled.
90   template:
91     type: str
92     description:
93       - The template used to define the host.
94       - Template cannot be modified after object creation.
95   check_command:
96     type: str
97     description:
98       - The command used to check if the host is alive.
99     default: "hostalive"
100   display_name:
101     type: str
102     description:
103       - The name used to display the host.
104       - If not specified, it defaults to the value of the O(name) parameter.
105   ip:
106     type: str
107     description:
108       - The IP address of the host.
109     required: true
110   variables:
111     type: dict
112     description:
113       - Dictionary of variables.
114 extends_documentation_fragment:
115   - ansible.builtin.url
116   - community.general.attributes
117 '''
118 
119 EXAMPLES = '''
120 - name: Add host to icinga
121   community.general.icinga2_host:
122     url: "https://icinga2.example.com"
123     url_username: "ansible"
124     url_password: "a_secret"
125     state: present
126     name: "{{ ansible_fqdn }}"
127     ip: "{{ ansible_default_ipv4.address }}"
128     variables:
129       foo: "bar"
130   delegate_to: 127.0.0.1
131 '''
132 
133 RETURN = '''
134 name:
135     description: The name used to create, modify or delete the host
136     type: str
137     returned: always
138 data:
139     description: The data structure used for create, modify or delete of the host
140     type: dict
141     returned: always
142 '''
143 
144 import json
145 
146 from ansible.module_utils.basic import AnsibleModule
147 from ansible.module_utils.urls import fetch_url, url_argument_spec
148 
149 
150 # ===========================================
151 # Icinga2 API class
152 #
153 class icinga2_api:
154     module = None
155 
156     def __init__(self, module):
157         self.module = module
158 
159     def call_url(self, path, data='', method='GET'):
160         headers = {
161             'Accept': 'application/json',
162             'X-HTTP-Method-Override': method,
163         }
164         url = self.module.params.get("url") + "/" + path
165         rsp, info = fetch_url(module=self.module, url=url, data=data, headers=headers, method=method, use_proxy=self.module.params['use_proxy'])
166         body = ''
167         if rsp:
168             body = json.loads(rsp.read())
169         if info['status'] >= 400:
170             body = info['body']
171         return {'code': info['status'], 'data': body}
172 
173     def check_connection(self):
174         ret = self.call_url('v1/status')
175         if ret['code'] == 200:
176             return True
177         return False
178 
179     def exists(self, hostname):
180         data = {
181             "filter": "match(\"" + hostname + "\", host.name)",
182         }
183         ret = self.call_url(
184             path="v1/objects/hosts",
185             data=self.module.jsonify(data)
186         )
187         if ret['code'] == 200:
188             if len(ret['data']['results']) == 1:
189                 return True
190         return False
191 
192     def create(self, hostname, data):
193         ret = self.call_url(
194             path="v1/objects/hosts/" + hostname,
195             data=self.module.jsonify(data),
196             method="PUT"
197         )
198         return ret
199 
200     def delete(self, hostname):
201         data = {"cascade": 1}
202         ret = self.call_url(
203             path="v1/objects/hosts/" + hostname,
204             data=self.module.jsonify(data),
205             method="DELETE"
206         )
207         return ret
208 
209     def modify(self, hostname, data):
210         ret = self.call_url(
211             path="v1/objects/hosts/" + hostname,
212             data=self.module.jsonify(data),
213             method="POST"
214         )
215         return ret
216 
217     def diff(self, hostname, data):
218         ret = self.call_url(
219             path="v1/objects/hosts/" + hostname,
220             method="GET"
221         )
222         changed = False
223         ic_data = ret['data']['results'][0]
224         for key in data['attrs']:
225             if key not in ic_data['attrs'].keys():
226                 changed = True
227             elif data['attrs'][key] != ic_data['attrs'][key]:
228                 changed = True
229         return changed
230 
231 
232 # ===========================================
233 # Module execution.
234 #
235 def main():
236     # use the predefined argument spec for url
237     argument_spec = url_argument_spec()
238     # add our own arguments
239     argument_spec.update(
240         state=dict(default="present", choices=["absent", "present"]),
241         name=dict(required=True, aliases=['host']),
242         zone=dict(),
243         template=dict(default=None),
244         check_command=dict(default="hostalive"),
245         display_name=dict(default=None),
246         ip=dict(required=True),
247         variables=dict(type='dict', default=None),
248     )
249 
250     # Define the main module
251     module = AnsibleModule(
252         argument_spec=argument_spec,
253         supports_check_mode=True
254     )
255 
256     state = module.params["state"]
257     name = module.params["name"]
258     zone = module.params["zone"]
259     template = []
260     if module.params["template"]:
261         template = [module.params["template"]]
262     check_command = module.params["check_command"]
263     ip = module.params["ip"]
264     display_name = module.params["display_name"]
265     if not display_name:
266         display_name = name
267     variables = module.params["variables"]
268 
269     try:
270         icinga = icinga2_api(module=module)
271         icinga.check_connection()
272     except Exception as e:
273         module.fail_json(msg="unable to connect to Icinga. Exception message: %s" % (e))
274 
275     data = {
276         'templates': template,
277         'attrs': {
278             'address': ip,
279             'display_name': display_name,
280             'check_command': check_command,
281             'zone': zone,
282             'vars.made_by': "ansible"
283         }
284     }
285 
286     for key, value in variables.items():
287         data['attrs']['vars.' + key] = value
288 
289     changed = False
290     if icinga.exists(name):
291         if state == "absent":
292             if module.check_mode:
293                 module.exit_json(changed=True, name=name, data=data)
294             else:
295                 try:
296                     ret = icinga.delete(name)
297                     if ret['code'] == 200:
298                         changed = True
299                     else:
300                         module.fail_json(msg="bad return code (%s) deleting host: '%s'" % (ret['code'], ret['data']))
301                 except Exception as e:
302                     module.fail_json(msg="exception deleting host: " + str(e))
303 
304         elif icinga.diff(name, data):
305             if module.check_mode:
306                 module.exit_json(changed=False, name=name, data=data)
307 
308             # Template attribute is not allowed in modification
309             del data['templates']
310 
311             ret = icinga.modify(name, data)
312 
313             if ret['code'] == 200:
314                 changed = True
315             else:
316                 module.fail_json(msg="bad return code (%s) modifying host: '%s'" % (ret['code'], ret['data']))
317 
318     else:
319         if state == "present":
320             if module.check_mode:
321                 changed = True
322             else:
323                 try:
324                     ret = icinga.create(name, data)
325                     if ret['code'] == 200:
326                         changed = True
327                     else:
328                         module.fail_json(msg="bad return code (%s) creating host: '%s'" % (ret['code'], ret['data']))
329                 except Exception as e:
330                     module.fail_json(msg="exception creating host: " + str(e))
331 
332     module.exit_json(changed=changed, name=name, data=data)
333 
334 
335 # import module snippets
336 if __name__ == '__main__':
337     main()
338
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/plugins/modules/icinga2_host.py b/plugins/modules/icinga2_host.py
--- a/plugins/modules/icinga2_host.py
+++ b/plugins/modules/icinga2_host.py
@@ -106,7 +106,7 @@
     type: str
     description:
       - The IP address of the host.
-    required: true
+      - This is no longer required since community.general 8.0.0.
   variables:
     type: dict
     description:
@@ -243,7 +243,7 @@
         template=dict(default=None),
         check_command=dict(default="hostalive"),
         display_name=dict(default=None),
-        ip=dict(required=True),
+        ip=dict(),
         variables=dict(type='dict', default=None),
     )

{"golden_diff": "diff --git a/plugins/modules/icinga2_host.py b/plugins/modules/icinga2_host.py\n--- a/plugins/modules/icinga2_host.py\n+++ b/plugins/modules/icinga2_host.py\n@@ -106,7 +106,7 @@\n     type: str\n     description:\n       - The IP address of the host.\n-    required: true\n+      - This is no longer required since community.general 8.0.0.\n   variables:\n     type: dict\n     description:\n@@ -243,7 +243,7 @@\n         template=dict(default=None),\n         check_command=dict(default=\"hostalive\"),\n         display_name=dict(default=None),\n-        ip=dict(required=True),\n+        ip=dict(),\n         variables=dict(type='dict', default=None),\n     )\n", "issue": "icinga2_host - ip should not be required\n### Summary\r\n\r\nHi all,\r\nas one can see in https://icinga.com/docs/icinga-2/latest/doc/09-object-types/#host the address variable is not mandatory, so IP should be optional in the plugin, too.\r\n\r\n\r\n### Issue Type\r\n\r\nBug Report\r\n\r\n### Component Name\r\n\r\nicinga2_host\r\n\r\n### Ansible Version\r\n\r\n```console (paste below)\r\n$ ansible --version\r\n2.11.4\r\n```\r\n\r\n\r\n### Community.general Version\r\n\r\n```console (paste below)\r\n$ ansible-galaxy collection list community.general\r\n5.5.0\r\n```\r\n\r\n\r\n### Configuration\r\n\r\n```console (paste below)\r\n\r\n```\r\n\r\n\r\n### OS / Environment\r\n\r\nUbuntu 22.04\r\n\r\n### Steps to Reproduce\r\nTry to create a host without given an IP\r\n\r\n\r\n### Expected Results\r\n\r\nAddress is optionally\r\n\r\n### Actual Results\r\n\r\nAddress is mandatory\r\n\r\n\r\n\r\n### Code of Conduct\r\n\r\n- [X] I agree to follow the Ansible Code of Conduct\n", "before_files": [{"content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\n# This module is proudly sponsored by CGI (www.cgi.com) and\n# KPN (www.kpn.com).\n# Copyright (c) Ansible project\n# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)\n# SPDX-License-Identifier: GPL-3.0-or-later\n\nfrom __future__ import absolute_import, division, print_function\n__metaclass__ = type\n\n\nDOCUMENTATION = '''\n---\nmodule: icinga2_host\nshort_description: Manage a host in Icinga2\ndescription:\n   - \"Add or remove a host to Icinga2 through the API.\"\n   - \"See U(https://www.icinga.com/docs/icinga2/latest/doc/12-icinga2-api/)\"\nauthor: \"Jurgen Brand (@t794104)\"\nattributes:\n  check_mode:\n    support: full\n  diff_mode:\n    support: none\noptions:\n  url:\n    type: str\n    description:\n      - HTTP, HTTPS, or FTP URL in the form (http|https|ftp)://[user[:pass]]@host.domain[:port]/path\n  use_proxy:\n    description:\n      - If V(false), it will not use a proxy, even if one is defined in\n        an environment variable on the target hosts.\n    type: bool\n    default: true\n  validate_certs:\n    description:\n      - If V(false), SSL certificates will not be validated. This should only be used\n        on personally controlled sites using self-signed certificates.\n    type: bool\n    default: true\n  url_username:\n    type: str\n    description:\n      - The username for use in HTTP basic authentication.\n      - This parameter can be used without O(url_password) for sites that allow empty passwords.\n  url_password:\n    type: str\n    description:\n        - The password for use in HTTP basic authentication.\n        - If the O(url_username) parameter is not specified, the O(url_password) parameter will not be used.\n  force_basic_auth:\n    description:\n      - httplib2, the library used by the uri module only sends authentication information when a webservice\n        responds to an initial request with a 401 status. Since some basic auth services do not properly\n        send a 401, logins will fail. This option forces the sending of the Basic authentication header\n        upon initial request.\n    type: bool\n    default: false\n  client_cert:\n    type: path\n    description:\n      - PEM formatted certificate chain file to be used for SSL client\n        authentication. This file can also include the key as well, and if\n        the key is included, O(client_key) is not required.\n  client_key:\n    type: path\n    description:\n      - PEM formatted file that contains your private key to be used for SSL\n        client authentication. If O(client_cert) contains both the certificate\n        and key, this option is not required.\n  state:\n    type: str\n    description:\n      - Apply feature state.\n    choices: [ \"present\", \"absent\" ]\n    default: present\n  name:\n    type: str\n    description:\n      - Name used to create / delete the host. This does not need to be the FQDN, but does needs to be unique.\n    required: true\n    aliases: [host]\n  zone:\n    type: str\n    description:\n      - The zone from where this host should be polled.\n  template:\n    type: str\n    description:\n      - The template used to define the host.\n      - Template cannot be modified after object creation.\n  check_command:\n    type: str\n    description:\n      - The command used to check if the host is alive.\n    default: \"hostalive\"\n  display_name:\n    type: str\n    description:\n      - The name used to display the host.\n      - If not specified, it defaults to the value of the O(name) parameter.\n  ip:\n    type: str\n    description:\n      - The IP address of the host.\n    required: true\n  variables:\n    type: dict\n    description:\n      - Dictionary of variables.\nextends_documentation_fragment:\n  - ansible.builtin.url\n  - community.general.attributes\n'''\n\nEXAMPLES = '''\n- name: Add host to icinga\n  community.general.icinga2_host:\n    url: \"https://icinga2.example.com\"\n    url_username: \"ansible\"\n    url_password: \"a_secret\"\n    state: present\n    name: \"{{ ansible_fqdn }}\"\n    ip: \"{{ ansible_default_ipv4.address }}\"\n    variables:\n      foo: \"bar\"\n  delegate_to: 127.0.0.1\n'''\n\nRETURN = '''\nname:\n    description: The name used to create, modify or delete the host\n    type: str\n    returned: always\ndata:\n    description: The data structure used for create, modify or delete of the host\n    type: dict\n    returned: always\n'''\n\nimport json\n\nfrom ansible.module_utils.basic import AnsibleModule\nfrom ansible.module_utils.urls import fetch_url, url_argument_spec\n\n\n# ===========================================\n# Icinga2 API class\n#\nclass icinga2_api:\n    module = None\n\n    def __init__(self, module):\n        self.module = module\n\n    def call_url(self, path, data='', method='GET'):\n        headers = {\n            'Accept': 'application/json',\n            'X-HTTP-Method-Override': method,\n        }\n        url = self.module.params.get(\"url\") + \"/\" + path\n        rsp, info = fetch_url(module=self.module, url=url, data=data, headers=headers, method=method, use_proxy=self.module.params['use_proxy'])\n        body = ''\n        if rsp:\n            body = json.loads(rsp.read())\n        if info['status'] >= 400:\n            body = info['body']\n        return {'code': info['status'], 'data': body}\n\n    def check_connection(self):\n        ret = self.call_url('v1/status')\n        if ret['code'] == 200:\n            return True\n        return False\n\n    def exists(self, hostname):\n        data = {\n            \"filter\": \"match(\\\"\" + hostname + \"\\\", host.name)\",\n        }\n        ret = self.call_url(\n            path=\"v1/objects/hosts\",\n            data=self.module.jsonify(data)\n        )\n        if ret['code'] == 200:\n            if len(ret['data']['results']) == 1:\n                return True\n        return False\n\n    def create(self, hostname, data):\n        ret = self.call_url(\n            path=\"v1/objects/hosts/\" + hostname,\n            data=self.module.jsonify(data),\n            method=\"PUT\"\n        )\n        return ret\n\n    def delete(self, hostname):\n        data = {\"cascade\": 1}\n        ret = self.call_url(\n            path=\"v1/objects/hosts/\" + hostname,\n            data=self.module.jsonify(data),\n            method=\"DELETE\"\n        )\n        return ret\n\n    def modify(self, hostname, data):\n        ret = self.call_url(\n            path=\"v1/objects/hosts/\" + hostname,\n            data=self.module.jsonify(data),\n            method=\"POST\"\n        )\n        return ret\n\n    def diff(self, hostname, data):\n        ret = self.call_url(\n            path=\"v1/objects/hosts/\" + hostname,\n            method=\"GET\"\n        )\n        changed = False\n        ic_data = ret['data']['results'][0]\n        for key in data['attrs']:\n            if key not in ic_data['attrs'].keys():\n                changed = True\n            elif data['attrs'][key] != ic_data['attrs'][key]:\n                changed = True\n        return changed\n\n\n# ===========================================\n# Module execution.\n#\ndef main():\n    # use the predefined argument spec for url\n    argument_spec = url_argument_spec()\n    # add our own arguments\n    argument_spec.update(\n        state=dict(default=\"present\", choices=[\"absent\", \"present\"]),\n        name=dict(required=True, aliases=['host']),\n        zone=dict(),\n        template=dict(default=None),\n        check_command=dict(default=\"hostalive\"),\n        display_name=dict(default=None),\n        ip=dict(required=True),\n        variables=dict(type='dict', default=None),\n    )\n\n    # Define the main module\n    module = AnsibleModule(\n        argument_spec=argument_spec,\n        supports_check_mode=True\n    )\n\n    state = module.params[\"state\"]\n    name = module.params[\"name\"]\n    zone = module.params[\"zone\"]\n    template = []\n    if module.params[\"template\"]:\n        template = [module.params[\"template\"]]\n    check_command = module.params[\"check_command\"]\n    ip = module.params[\"ip\"]\n    display_name = module.params[\"display_name\"]\n    if not display_name:\n        display_name = name\n    variables = module.params[\"variables\"]\n\n    try:\n        icinga = icinga2_api(module=module)\n        icinga.check_connection()\n    except Exception as e:\n        module.fail_json(msg=\"unable to connect to Icinga. Exception message: %s\" % (e))\n\n    data = {\n        'templates': template,\n        'attrs': {\n            'address': ip,\n            'display_name': display_name,\n            'check_command': check_command,\n            'zone': zone,\n            'vars.made_by': \"ansible\"\n        }\n    }\n\n    for key, value in variables.items():\n        data['attrs']['vars.' + key] = value\n\n    changed = False\n    if icinga.exists(name):\n        if state == \"absent\":\n            if module.check_mode:\n                module.exit_json(changed=True, name=name, data=data)\n            else:\n                try:\n                    ret = icinga.delete(name)\n                    if ret['code'] == 200:\n                        changed = True\n                    else:\n                        module.fail_json(msg=\"bad return code (%s) deleting host: '%s'\" % (ret['code'], ret['data']))\n                except Exception as e:\n                    module.fail_json(msg=\"exception deleting host: \" + str(e))\n\n        elif icinga.diff(name, data):\n            if module.check_mode:\n                module.exit_json(changed=False, name=name, data=data)\n\n            # Template attribute is not allowed in modification\n            del data['templates']\n\n            ret = icinga.modify(name, data)\n\n            if ret['code'] == 200:\n                changed = True\n            else:\n                module.fail_json(msg=\"bad return code (%s) modifying host: '%s'\" % (ret['code'], ret['data']))\n\n    else:\n        if state == \"present\":\n            if module.check_mode:\n                changed = True\n            else:\n                try:\n                    ret = icinga.create(name, data)\n                    if ret['code'] == 200:\n                        changed = True\n                    else:\n                        module.fail_json(msg=\"bad return code (%s) creating host: '%s'\" % (ret['code'], ret['data']))\n                except Exception as e:\n                    module.fail_json(msg=\"exception creating host: \" + str(e))\n\n    module.exit_json(changed=changed, name=name, data=data)\n\n\n# import module snippets\nif __name__ == '__main__':\n    main()\n", "path": "plugins/modules/icinga2_host.py"}], "after_files": [{"content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\n# This module is proudly sponsored by CGI (www.cgi.com) and\n# KPN (www.kpn.com).\n# Copyright (c) Ansible project\n# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)\n# SPDX-License-Identifier: GPL-3.0-or-later\n\nfrom __future__ import absolute_import, division, print_function\n__metaclass__ = type\n\n\nDOCUMENTATION = '''\n---\nmodule: icinga2_host\nshort_description: Manage a host in Icinga2\ndescription:\n   - \"Add or remove a host to Icinga2 through the API.\"\n   - \"See U(https://www.icinga.com/docs/icinga2/latest/doc/12-icinga2-api/)\"\nauthor: \"Jurgen Brand (@t794104)\"\nattributes:\n  check_mode:\n    support: full\n  diff_mode:\n    support: none\noptions:\n  url:\n    type: str\n    description:\n      - HTTP, HTTPS, or FTP URL in the form (http|https|ftp)://[user[:pass]]@host.domain[:port]/path\n  use_proxy:\n    description:\n      - If V(false), it will not use a proxy, even if one is defined in\n        an environment variable on the target hosts.\n    type: bool\n    default: true\n  validate_certs:\n    description:\n      - If V(false), SSL certificates will not be validated. This should only be used\n        on personally controlled sites using self-signed certificates.\n    type: bool\n    default: true\n  url_username:\n    type: str\n    description:\n      - The username for use in HTTP basic authentication.\n      - This parameter can be used without O(url_password) for sites that allow empty passwords.\n  url_password:\n    type: str\n    description:\n        - The password for use in HTTP basic authentication.\n        - If the O(url_username) parameter is not specified, the O(url_password) parameter will not be used.\n  force_basic_auth:\n    description:\n      - httplib2, the library used by the uri module only sends authentication information when a webservice\n        responds to an initial request with a 401 status. Since some basic auth services do not properly\n        send a 401, logins will fail. This option forces the sending of the Basic authentication header\n        upon initial request.\n    type: bool\n    default: false\n  client_cert:\n    type: path\n    description:\n      - PEM formatted certificate chain file to be used for SSL client\n        authentication. This file can also include the key as well, and if\n        the key is included, O(client_key) is not required.\n  client_key:\n    type: path\n    description:\n      - PEM formatted file that contains your private key to be used for SSL\n        client authentication. If O(client_cert) contains both the certificate\n        and key, this option is not required.\n  state:\n    type: str\n    description:\n      - Apply feature state.\n    choices: [ \"present\", \"absent\" ]\n    default: present\n  name:\n    type: str\n    description:\n      - Name used to create / delete the host. This does not need to be the FQDN, but does needs to be unique.\n    required: true\n    aliases: [host]\n  zone:\n    type: str\n    description:\n      - The zone from where this host should be polled.\n  template:\n    type: str\n    description:\n      - The template used to define the host.\n      - Template cannot be modified after object creation.\n  check_command:\n    type: str\n    description:\n      - The command used to check if the host is alive.\n    default: \"hostalive\"\n  display_name:\n    type: str\n    description:\n      - The name used to display the host.\n      - If not specified, it defaults to the value of the O(name) parameter.\n  ip:\n    type: str\n    description:\n      - The IP address of the host.\n      - This is no longer required since community.general 8.0.0.\n  variables:\n    type: dict\n    description:\n      - Dictionary of variables.\nextends_documentation_fragment:\n  - ansible.builtin.url\n  - community.general.attributes\n'''\n\nEXAMPLES = '''\n- name: Add host to icinga\n  community.general.icinga2_host:\n    url: \"https://icinga2.example.com\"\n    url_username: \"ansible\"\n    url_password: \"a_secret\"\n    state: present\n    name: \"{{ ansible_fqdn }}\"\n    ip: \"{{ ansible_default_ipv4.address }}\"\n    variables:\n      foo: \"bar\"\n  delegate_to: 127.0.0.1\n'''\n\nRETURN = '''\nname:\n    description: The name used to create, modify or delete the host\n    type: str\n    returned: always\ndata:\n    description: The data structure used for create, modify or delete of the host\n    type: dict\n    returned: always\n'''\n\nimport json\n\nfrom ansible.module_utils.basic import AnsibleModule\nfrom ansible.module_utils.urls import fetch_url, url_argument_spec\n\n\n# ===========================================\n# Icinga2 API class\n#\nclass icinga2_api:\n    module = None\n\n    def __init__(self, module):\n        self.module = module\n\n    def call_url(self, path, data='', method='GET'):\n        headers = {\n            'Accept': 'application/json',\n            'X-HTTP-Method-Override': method,\n        }\n        url = self.module.params.get(\"url\") + \"/\" + path\n        rsp, info = fetch_url(module=self.module, url=url, data=data, headers=headers, method=method, use_proxy=self.module.params['use_proxy'])\n        body = ''\n        if rsp:\n            body = json.loads(rsp.read())\n        if info['status'] >= 400:\n            body = info['body']\n        return {'code': info['status'], 'data': body}\n\n    def check_connection(self):\n        ret = self.call_url('v1/status')\n        if ret['code'] == 200:\n            return True\n        return False\n\n    def exists(self, hostname):\n        data = {\n            \"filter\": \"match(\\\"\" + hostname + \"\\\", host.name)\",\n        }\n        ret = self.call_url(\n            path=\"v1/objects/hosts\",\n            data=self.module.jsonify(data)\n        )\n        if ret['code'] == 200:\n            if len(ret['data']['results']) == 1:\n                return True\n        return False\n\n    def create(self, hostname, data):\n        ret = self.call_url(\n            path=\"v1/objects/hosts/\" + hostname,\n            data=self.module.jsonify(data),\n            method=\"PUT\"\n        )\n        return ret\n\n    def delete(self, hostname):\n        data = {\"cascade\": 1}\n        ret = self.call_url(\n            path=\"v1/objects/hosts/\" + hostname,\n            data=self.module.jsonify(data),\n            method=\"DELETE\"\n        )\n        return ret\n\n    def modify(self, hostname, data):\n        ret = self.call_url(\n            path=\"v1/objects/hosts/\" + hostname,\n            data=self.module.jsonify(data),\n            method=\"POST\"\n        )\n        return ret\n\n    def diff(self, hostname, data):\n        ret = self.call_url(\n            path=\"v1/objects/hosts/\" + hostname,\n            method=\"GET\"\n        )\n        changed = False\n        ic_data = ret['data']['results'][0]\n        for key in data['attrs']:\n            if key not in ic_data['attrs'].keys():\n                changed = True\n            elif data['attrs'][key] != ic_data['attrs'][key]:\n                changed = True\n        return changed\n\n\n# ===========================================\n# Module execution.\n#\ndef main():\n    # use the predefined argument spec for url\n    argument_spec = url_argument_spec()\n    # add our own arguments\n    argument_spec.update(\n        state=dict(default=\"present\", choices=[\"absent\", \"present\"]),\n        name=dict(required=True, aliases=['host']),\n        zone=dict(),\n        template=dict(default=None),\n        check_command=dict(default=\"hostalive\"),\n        display_name=dict(default=None),\n        ip=dict(),\n        variables=dict(type='dict', default=None),\n    )\n\n    # Define the main module\n    module = AnsibleModule(\n        argument_spec=argument_spec,\n        supports_check_mode=True\n    )\n\n    state = module.params[\"state\"]\n    name = module.params[\"name\"]\n    zone = module.params[\"zone\"]\n    template = []\n    if module.params[\"template\"]:\n        template = [module.params[\"template\"]]\n    check_command = module.params[\"check_command\"]\n    ip = module.params[\"ip\"]\n    display_name = module.params[\"display_name\"]\n    if not display_name:\n        display_name = name\n    variables = module.params[\"variables\"]\n\n    try:\n        icinga = icinga2_api(module=module)\n        icinga.check_connection()\n    except Exception as e:\n        module.fail_json(msg=\"unable to connect to Icinga. Exception message: %s\" % (e))\n\n    data = {\n        'templates': template,\n        'attrs': {\n            'address': ip,\n            'display_name': display_name,\n            'check_command': check_command,\n            'zone': zone,\n            'vars.made_by': \"ansible\"\n        }\n    }\n\n    for key, value in variables.items():\n        data['attrs']['vars.' + key] = value\n\n    changed = False\n    if icinga.exists(name):\n        if state == \"absent\":\n            if module.check_mode:\n                module.exit_json(changed=True, name=name, data=data)\n            else:\n                try:\n                    ret = icinga.delete(name)\n                    if ret['code'] == 200:\n                        changed = True\n                    else:\n                        module.fail_json(msg=\"bad return code (%s) deleting host: '%s'\" % (ret['code'], ret['data']))\n                except Exception as e:\n                    module.fail_json(msg=\"exception deleting host: \" + str(e))\n\n        elif icinga.diff(name, data):\n            if module.check_mode:\n                module.exit_json(changed=False, name=name, data=data)\n\n            # Template attribute is not allowed in modification\n            del data['templates']\n\n            ret = icinga.modify(name, data)\n\n            if ret['code'] == 200:\n                changed = True\n            else:\n                module.fail_json(msg=\"bad return code (%s) modifying host: '%s'\" % (ret['code'], ret['data']))\n\n    else:\n        if state == \"present\":\n            if module.check_mode:\n                changed = True\n            else:\n                try:\n                    ret = icinga.create(name, data)\n                    if ret['code'] == 200:\n                        changed = True\n                    else:\n                        module.fail_json(msg=\"bad return code (%s) creating host: '%s'\" % (ret['code'], ret['data']))\n                except Exception as e:\n                    module.fail_json(msg=\"exception creating host: \" + str(e))\n\n    module.exit_json(changed=changed, name=name, data=data)\n\n\n# import module snippets\nif __name__ == '__main__':\n    main()\n", "path": "plugins/modules/icinga2_host.py"}]}