1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00
community.general/plugins/modules/icinga2_host.py
patchback[bot] ee03af599c
[PR #6748/806f6da1 backport][stable-6] icinga2_host: fix a key error when modifying an existing host (#6752)
icinga2_host: fix a key error when modifying an existing host (#6748)

* Initialize `template` variable. Add changelog fragment.

* Update changelogs/fragments/6286-icinga2_host-template-and-template-vars.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* icinga2_host: fix a key error when updating a host

* Changelog fragment.

* Update changelog fragment with correct PR number.

---------

Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 806f6da16b)

Co-authored-by: yoannlr <32494673+yoannlr@users.noreply.github.com>
2023-06-20 19:23:06 +02:00

337 lines
10 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
# This module is proudly sponsored by CGI (www.cgi.com) and
# KPN (www.kpn.com).
# Copyright (c) Ansible project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: icinga2_host
short_description: Manage a host in Icinga2
description:
- "Add or remove a host to Icinga2 through the API."
- "See U(https://www.icinga.com/docs/icinga2/latest/doc/12-icinga2-api/)"
author: "Jurgen Brand (@t794104)"
attributes:
check_mode:
support: full
diff_mode:
support: none
options:
url:
type: str
description:
- HTTP, HTTPS, or FTP URL in the form (http|https|ftp)://[user[:pass]]@host.domain[:port]/path
use_proxy:
description:
- If C(false), it will not use a proxy, even if one is defined in
an environment variable on the target hosts.
type: bool
default: true
validate_certs:
description:
- If C(false), SSL certificates will not be validated. This should only be used
on personally controlled sites using self-signed certificates.
type: bool
default: true
url_username:
type: str
description:
- The username for use in HTTP basic authentication.
- This parameter can be used without C(url_password) for sites that allow empty passwords.
url_password:
type: str
description:
- The password for use in HTTP basic authentication.
- If the C(url_username) parameter is not specified, the C(url_password) parameter will not be used.
force_basic_auth:
description:
- httplib2, the library used by the uri module only sends authentication information when a webservice
responds to an initial request with a 401 status. Since some basic auth services do not properly
send a 401, logins will fail. This option forces the sending of the Basic authentication header
upon initial request.
type: bool
default: false
client_cert:
type: path
description:
- PEM formatted certificate chain file to be used for SSL client
authentication. This file can also include the key as well, and if
the key is included, C(client_key) is not required.
client_key:
type: path
description:
- PEM formatted file that contains your private key to be used for SSL
client authentication. If C(client_cert) contains both the certificate
and key, this option is not required.
state:
type: str
description:
- Apply feature state.
choices: [ "present", "absent" ]
default: present
name:
type: str
description:
- Name used to create / delete the host. This does not need to be the FQDN, but does needs to be unique.
required: true
aliases: [host]
zone:
type: str
description:
- The zone from where this host should be polled.
template:
type: str
description:
- The template used to define the host.
- Template cannot be modified after object creation.
check_command:
type: str
description:
- The command used to check if the host is alive.
default: "hostalive"
display_name:
type: str
description:
- The name used to display the host.
- If not specified, it defaults to the value of the I(name) parameter.
ip:
type: str
description:
- The IP address of the host.
required: true
variables:
type: dict
description:
- Dictionary of variables.
extends_documentation_fragment:
- ansible.builtin.url
- community.general.attributes
'''
EXAMPLES = '''
- name: Add host to icinga
community.general.icinga2_host:
url: "https://icinga2.example.com"
url_username: "ansible"
url_password: "a_secret"
state: present
name: "{{ ansible_fqdn }}"
ip: "{{ ansible_default_ipv4.address }}"
variables:
foo: "bar"
delegate_to: 127.0.0.1
'''
RETURN = '''
name:
description: The name used to create, modify or delete the host
type: str
returned: always
data:
description: The data structure used for create, modify or delete of the host
type: dict
returned: always
'''
import json
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.urls import fetch_url, url_argument_spec
# ===========================================
# Icinga2 API class
#
class icinga2_api:
module = None
def __init__(self, module):
self.module = module
def call_url(self, path, data='', method='GET'):
headers = {
'Accept': 'application/json',
'X-HTTP-Method-Override': method,
}
url = self.module.params.get("url") + "/" + path
rsp, info = fetch_url(module=self.module, url=url, data=data, headers=headers, method=method, use_proxy=self.module.params['use_proxy'])
body = ''
if rsp:
body = json.loads(rsp.read())
if info['status'] >= 400:
body = info['body']
return {'code': info['status'], 'data': body}
def check_connection(self):
ret = self.call_url('v1/status')
if ret['code'] == 200:
return True
return False
def exists(self, hostname):
data = {
"filter": "match(\"" + hostname + "\", host.name)",
}
ret = self.call_url(
path="v1/objects/hosts",
data=self.module.jsonify(data)
)
if ret['code'] == 200:
if len(ret['data']['results']) == 1:
return True
return False
def create(self, hostname, data):
ret = self.call_url(
path="v1/objects/hosts/" + hostname,
data=self.module.jsonify(data),
method="PUT"
)
return ret
def delete(self, hostname):
data = {"cascade": 1}
ret = self.call_url(
path="v1/objects/hosts/" + hostname,
data=self.module.jsonify(data),
method="DELETE"
)
return ret
def modify(self, hostname, data):
ret = self.call_url(
path="v1/objects/hosts/" + hostname,
data=self.module.jsonify(data),
method="POST"
)
return ret
def diff(self, hostname, data):
ret = self.call_url(
path="v1/objects/hosts/" + hostname,
method="GET"
)
changed = False
ic_data = ret['data']['results'][0]
for key in data['attrs']:
if key not in ic_data['attrs'].keys():
changed = True
elif data['attrs'][key] != ic_data['attrs'][key]:
changed = True
return changed
# ===========================================
# Module execution.
#
def main():
# use the predefined argument spec for url
argument_spec = url_argument_spec()
# add our own arguments
argument_spec.update(
state=dict(default="present", choices=["absent", "present"]),
name=dict(required=True, aliases=['host']),
zone=dict(),
template=dict(default=None),
check_command=dict(default="hostalive"),
display_name=dict(default=None),
ip=dict(required=True),
variables=dict(type='dict', default=None),
)
# Define the main module
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True
)
state = module.params["state"]
name = module.params["name"]
zone = module.params["zone"]
template = []
if module.params["template"]:
template = [module.params["template"]]
check_command = module.params["check_command"]
ip = module.params["ip"]
display_name = module.params["display_name"]
if not display_name:
display_name = name
variables = module.params["variables"]
try:
icinga = icinga2_api(module=module)
icinga.check_connection()
except Exception as e:
module.fail_json(msg="unable to connect to Icinga. Exception message: %s" % (e))
data = {
'templates': template,
'attrs': {
'address': ip,
'display_name': display_name,
'check_command': check_command,
'zone': zone,
'vars.made_by': "ansible"
}
}
for key, value in variables.items():
data['attrs']['vars.' + key] = value
changed = False
if icinga.exists(name):
if state == "absent":
if module.check_mode:
module.exit_json(changed=True, name=name, data=data)
else:
try:
ret = icinga.delete(name)
if ret['code'] == 200:
changed = True
else:
module.fail_json(msg="bad return code (%s) deleting host: '%s'" % (ret['code'], ret['data']))
except Exception as e:
module.fail_json(msg="exception deleting host: " + str(e))
elif icinga.diff(name, data):
if module.check_mode:
module.exit_json(changed=False, name=name, data=data)
# Template attribute is not allowed in modification
del data['templates']
ret = icinga.modify(name, data)
if ret['code'] == 200:
changed = True
else:
module.fail_json(msg="bad return code (%s) modifying host: '%s'" % (ret['code'], ret['data']))
else:
if state == "present":
if module.check_mode:
changed = True
else:
try:
ret = icinga.create(name, data)
if ret['code'] == 200:
changed = True
else:
module.fail_json(msg="bad return code (%s) creating host: '%s'" % (ret['code'], ret['data']))
except Exception as e:
module.fail_json(msg="exception creating host: " + str(e))
module.exit_json(changed=changed, name=name, data=data)
# import module snippets
if __name__ == '__main__':
main()