diff --git a/lib/ansible/modules/monitoring/zabbix/zabbix_mediatype.py b/lib/ansible/modules/monitoring/zabbix/zabbix_mediatype.py new file mode 100644 index 00000000000..64836981fb1 --- /dev/null +++ b/lib/ansible/modules/monitoring/zabbix/zabbix_mediatype.py @@ -0,0 +1,688 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# 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'} + + +DOCUMENTATION = ''' +--- +module: zabbix_mediatype +short_description: Create/Update/Delete Zabbix media types +description: + - This module allows you to create, modify and delete Zabbix media types. +version_added: "2.9" +author: + - Ruben Tsirunyan (@rubentsirunyan) +requirements: + - zabbix-api + +options: + name: + type: 'str' + description: + - Name of the media type. + required: true + state: + type: 'str' + description: + - Desired state of the mediatype. + - On C(present), it will create a medatype if it does not exist or update the mediatype if the associated data is different. + - On C(absent), it will remove the mediatype if it exists. + choices: + - present + - absent + default: 'present' + required: true + type: + type: 'str' + description: + - Type of the media type. + choices: + - email + - script + - sms + - jabber + - ez_texting + required: true + status: + type: 'str' + description: + - Whether the media type is enabled or no. + choices: + - enabled + - disabled + default: 'enabled' + max_sessions: + type: 'int' + description: + - The maximum number of alerts that can be processed in parallel. + - Possible value is 1 when I(type=sms) and 0-100 otherwise. + default: 1 + max_attempts: + type: 'int' + description: + - The maximum number of attempts to send an alert. + - Possible range is 0-10 + default: 3 + attempt_interval: + type: 'int' + description: + - The interval between retry attempts. + - Possible range is 0-60 + default: 10 + script_name: + type: 'str' + description: + - The name of the executed script. + - Required when I(type=script). + script_params: + type: 'list' + description: + - List of script parameters. + - Required when I(type=script). + gsm_modem: + type: 'str' + description: + - Serial device name of the gsm modem. + - Required when I(type=sms). + username: + type: 'str' + description: + - Username or Jabber identifier. + - Required when I(type=jabber) or I(type=ez_texting). + - Required when I(type=email) and I(smtp_authentication=true). + password: + type: 'str' + description: + - Authentication password. + - Required when I(type=jabber) or I(type=ez_texting). + - Required when I(type=email) and I(smtp_authentication=true). + smtp_server: + type: 'str' + description: + - SMTP server host. + - Required when I(type=email). + default: 'localhost' + smtp_server_port: + type: 'int' + description: + - SMTP server port. + - Required when I(type=email). + default: 25 + smtp_helo: + type: 'str' + description: + - SMTP HELO. + - Required when I(type=email). + default: 'localhost' + smtp_email: + type: 'str' + description: + - Email address from which notifications will be sent. + - Required when I(type=email). + smtp_authentication: + type: 'bool' + description: + - Whether SMTP authetication with username and password should be enabled or not. + - If set to C(true), C(username) and C(password) should be specified. + default: false + smtp_security: + type: 'str' + description: + - SMTP connection security level to use. + choices: + - None + - STARTTLS + - SSL/TLS + smtp_verify_host: + type: 'bool' + description: + - SSL verify host for SMTP. + - Can be specified when I(smtp_security=STARTTLS) or I(smtp_security=SSL/TLS) + default: false + smtp_verify_peer: + type: 'bool' + description: + - SSL verify peer for SMTP. + - Can be specified when I(smtp_security=STARTTLS) or I(smtp_security=SSL/TLS) + default: false + message_text_limit: + type: 'str' + description: + - The message text limit. + - Required when I(type=ez_texting). + - 160 characters for USA and 136 characters for Canada. + choices: + - USA + - Canada +extends_documentation_fragment: + - zabbix + +''' + +RETURN = ''' +''' + +EXAMPLES = ''' +- name: 'Create an email mediatype with SMTP authentication' + zabbix_mediatype: + name: "Ops email" + server_url: "http://example.com/zabbix/" + login_user: Admin + login_password: "zabbix" + type: 'email' + smtp_server: 'example.com' + smtp_server_port: 2000 + smtp_email: 'ops@example.com' + smtp_authentication: true + username: 'smtp_user' + password: 'smtp_pass' + +- name: 'Create a script mediatype' + zabbix_mediatype: + name: "my script" + server_url: "http://example.com/zabbix/" + login_user: Admin + login_password: "zabbix" + type: 'script' + script_name: 'my_script.py' + script_params: + - 'arg1' + - 'arg2' + +- name: 'Create a jabber mediatype' + zabbix_mediatype: + name: "My jabber" + server_url: "http://example.com/zabbix/" + login_user: Admin + login_password: "zabbix" + type: 'jabber' + username: 'jabber_id' + password: 'jabber_pass' + +- name: 'Create an SMS mediatype' + zabbix_mediatype: + name: "My SMS Mediatype" + server_url: "http://example.com/zabbix/" + login_user: Admin + login_password: "zabbix" + type: 'sms' + gsm_modem: '/dev/ttyS0' +''' + + +import traceback + + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible.module_utils.common.text.converters import container_to_bytes + + +try: + from zabbix_api import ZabbixAPI + HAS_ZABBIX_API = True +except ImportError: + ZBX_IMP_ERR = traceback.format_exc() + HAS_ZABBIX_API = False + + +def to_numeric_value(value, strs): + return strs.get(value) + + +def validate_params(module, params): + """Validates arguments that are required together. + + Fails the module with the message that shows the missing + requirements if there are some. + + Args: + module: AnsibleModule object. + params (list): Each element of this list + is a list like + ['argument_key', 'argument_value', ['required_arg_1', + 'required_arg_2']]. + Format is the same as `required_if` parameter of AnsibleModule. + """ + for param in params: + if module.params[param[0]] == param[1]: + if None in [module.params[i] for i in param[2]]: + module.fail_json( + msg="Following arguments are required when {key} is {value}: {arguments}".format( + key=param[0], + value=param[1], + arguments=', '.join(param[2]) + ) + ) + + +def construct_parameters(**kwargs): + """Translates data to a format suitable for Zabbix API and filters + the ones that are related to the specified mediatype type. + + Args: + **kwargs: Arguments passed to the module. + + Returns: + A dictionary of arguments that are related to kwargs['transport_type'], + and are in a format that is understandable by Zabbix API. + """ + if kwargs['transport_type'] == 'email': + return dict( + description=kwargs['name'], + status=to_numeric_value(kwargs['status'], + {'enabled': '0', + 'disabled': '1'}), + type=to_numeric_value(kwargs['transport_type'], + {'email': '0', + 'script': '1', + 'sms': '2', + 'jabber': '3', + 'ez_texting': '100'}), + maxsessions=str(kwargs['max_sessions']), + maxattempts=str(kwargs['max_attempts']), + attempt_interval=str(kwargs['attempt_interval']), + smtp_server=kwargs['smtp_server'], + smtp_port=str(kwargs['smtp_server_port']), + smtp_helo=kwargs['smtp_helo'], + smtp_email=kwargs['smtp_email'], + smtp_security=to_numeric_value(str(kwargs['smtp_security']), + {'None': '0', + 'STARTTLS': '1', + 'SSL/TLS': '2'}), + smtp_authentication=to_numeric_value(str(kwargs['smtp_authentication']), + {'False': '0', + 'True': '1'}), + smtp_verify_host=to_numeric_value(str(kwargs['smtp_verify_host']), + {'False': '0', + 'True': '1'}), + smtp_verify_peer=to_numeric_value(str(kwargs['smtp_verify_peer']), + {'False': '0', + 'True': '1'}), + username=kwargs['username'], + passwd=kwargs['password'] + ) + + elif kwargs['transport_type'] == 'script': + if kwargs['script_params'] is None: + _script_params = '' # ZBX-15706 + else: + _script_params = '\n'.join(str(i) for i in kwargs['script_params']) + '\n' + return dict( + description=kwargs['name'], + status=to_numeric_value(kwargs['status'], + {'enabled': '0', + 'disabled': '1'}), + type=to_numeric_value(kwargs['transport_type'], + {'email': '0', + 'script': '1', + 'sms': '2', + 'jabber': '3', + 'ez_texting': '100'}), + maxsessions=str(kwargs['max_sessions']), + maxattempts=str(kwargs['max_attempts']), + attempt_interval=str(kwargs['attempt_interval']), + exec_path=kwargs['script_name'], + exec_params=_script_params + ) + elif kwargs['transport_type'] == 'sms': + return dict( + description=kwargs['name'], + status=to_numeric_value(kwargs['status'], + {'enabled': '0', + 'disabled': '1'}), + type=to_numeric_value(kwargs['transport_type'], + {'email': '0', + 'script': '1', + 'sms': '2', + 'jabber': '3', + 'ez_texting': '100'}), + maxsessions=str(kwargs['max_sessions']), + maxattempts=str(kwargs['max_attempts']), + attempt_interval=str(kwargs['attempt_interval']), + gsm_modem=kwargs['gsm_modem'] + ) + elif kwargs['transport_type'] == 'jabber': + return dict( + description=kwargs['name'], + status=to_numeric_value(kwargs['status'], + {'enabled': '0', + 'disabled': '1'}), + type=to_numeric_value(kwargs['transport_type'], + {'email': '0', + 'script': '1', + 'sms': '2', + 'jabber': '3', + 'ez_texting': '100'}), + maxsessions=str(kwargs['max_sessions']), + maxattempts=str(kwargs['max_attempts']), + attempt_interval=str(kwargs['attempt_interval']), + username=kwargs['username'], + passwd=kwargs['password'] + ) + elif kwargs['transport_type'] == 'ez_texting': + return dict( + description=kwargs['name'], + status=to_numeric_value(kwargs['status'], + {'enabled': '0', + 'disabled': '1'}), + type=to_numeric_value(kwargs['transport_type'], + {'email': '0', + 'script': '1', + 'sms': '2', + 'jabber': '3', + 'ez_texting': '100'}), + maxsessions=str(kwargs['max_sessions']), + maxattempts=str(kwargs['max_attempts']), + attempt_interval=str(kwargs['attempt_interval']), + username=kwargs['username'], + passwd=kwargs['password'], + exec_path=to_numeric_value(kwargs['message_text_limit'], + {'USA': '0', + 'Canada': '1'}), + ) + + +def check_if_mediatype_exists(module, zbx, name): + """Checks if mediatype exists. + + Args: + module: AnsibleModule object + zbx: ZabbixAPI object + name: Zabbix mediatype name + + Returns: + Tuple of (True, `id of the mediatype`) if mediatype exists, (False, None) otherwise + """ + try: + mediatype_list = zbx.mediatype.get({ + 'output': 'extend', + 'filter': {'description': [name]} + }) + if len(mediatype_list) < 1: + return False, None + else: + return True, mediatype_list[0]['mediatypeid'] + except Exception as e: + module.fail_json(msg="Failed to get ID of the mediatype '{name}': {e}".format(name=name, e=e)) + + +def diff(existing, new): + """Constructs the diff for Ansible's --diff option. + + Args: + existing (dict): Existing mediatype data. + new (dict): New mediatype data. + + Returns: + A dictionary like {'before': existing, 'after': new} + with filtered empty values. + """ + before = {} + after = {} + for key in new: + before[key] = existing[key] + if new[key] is None: + after[key] = '' + else: + after[key] = new[key] + return {'before': before, 'after': after} + + +def get_update_params(module, zbx, mediatype_id, **kwargs): + """Filters only the parameters that are different and need to be updated. + + Args: + module: AnsibleModule object. + zbx: ZabbixAPI object. + mediatype_id (int): ID of the mediatype to be updated. + **kwargs: Parameters for the new mediatype. + + Returns: + A tuple where the first element is a dictionary of parameters + that need to be updated and the second one is a dictionary + returned by diff() function with + existing mediatype data and new params passed to it. + """ + existing_mediatype = container_to_bytes(zbx.mediatype.get({ + 'output': 'extend', + 'mediatypeids': [mediatype_id] + })[0]) + + if existing_mediatype['type'] != kwargs['type']: + return kwargs, diff(existing_mediatype, kwargs) + else: + params_to_update = {} + for key in kwargs: + if (not (kwargs[key] is None and existing_mediatype[key] == '')) and kwargs[key] != existing_mediatype[key]: + params_to_update[key] = kwargs[key] + return params_to_update, diff(existing_mediatype, kwargs) + + +def delete_mediatype(module, zbx, mediatype_id): + try: + return zbx.mediatype.delete([mediatype_id]) + except Exception as e: + module.fail_json(msg="Failed to delete mediatype '{_id}': {e}".format(_id=mediatype_id, e=e)) + + +def update_mediatype(module, zbx, **kwargs): + try: + pass + mediatype_id = zbx.mediatype.update(kwargs) + except Exception as e: + module.fail_json(msg="Failed to update mediatype '{_id}': {e}".format(_id=kwargs['mediatypeid'], e=e)) + + +def create_mediatype(module, zbx, **kwargs): + try: + mediatype_id = zbx.mediatype.create(kwargs) + except Exception as e: + module.fail_json(msg="Failed to create mediatype '{name}': {e}".format(name=kwargs['description'], e=e)) + + +def main(): + argument_spec = dict( + server_url=dict(type='str', required=True, aliases=['url']), + login_user=dict(type='str', required=True), + login_password=dict(type='str', required=True, no_log=True), + http_login_user=dict(type='str', required=False, default=None), + http_login_password=dict(type='str', required=False, default=None, no_log=True), + validate_certs=dict(type='bool', required=False, default=True), timeout=dict(type='int', default=10), + name=dict(type='str', required=True), + state=dict(type='str', default='present', choices=['present', 'absent']), + type=dict(type='str', choices=['email', 'script', 'sms', 'jabber', 'ez_texting'], required=True), + status=dict(type='str', default='enabled', choices=['enabled', 'disabled'], required=False), + max_sessions=dict(type='int', default=1, required=False), + max_attempts=dict(type='int', default=3, required=False), + attempt_interval=dict(type='int', default=10, required=False), + # Script + script_name=dict(type='str', required=False), + script_params=dict(type='list', required=False), + # SMS + gsm_modem=dict(type='str', required=False), + # Jabber + username=dict(type='str', required=False), + password=dict(type='str', required=False, no_log=True), + # Email + smtp_server=dict(type='str', default='localhost', required=False), + smtp_server_port=dict(type='int', default=25, required=False), + smtp_helo=dict(type='str', default='localhost', required=False), + smtp_email=dict(type='str', required=False), + smtp_security=dict(type='str', required=False, choices=['None', 'STARTTLS', 'SSL/TLS']), + smtp_authentication=dict(type='bool', default=False, required=False), + smtp_verify_host=dict(type='bool', default=False, required=False), + smtp_verify_peer=dict(type='bool', default=False, required=False), + # EZ Text + message_text_limit=dict(type='str', required=False, choices=['USA', 'Canada']) + ) + + required_params = [ + ['type', 'email', ['smtp_email']], + ['type', 'script', ['script_name']], + ['type', 'sms', ['gsm_modem']], + ['type', 'jabber', ['username', 'password']], + ['type', 'ez_texting', ['username', 'password', 'message_text_limit']], + ['smtp_authentication', True, ['username', 'password']] + ] + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + + if module.params['state'] == 'present': + validate_params(module, required_params) + + if not HAS_ZABBIX_API: + module.fail_json(msg=missing_required_lib('zabbix-api', url='https://pypi.org/project/zabbix-api/'), exception=ZBX_IMP_ERR) + + server_url = module.params['server_url'] + login_user = module.params['login_user'] + login_password = module.params['login_password'] + http_login_user = module.params['http_login_user'] + http_login_password = module.params['http_login_password'] + validate_certs = module.params['validate_certs'] + state = module.params['state'] + timeout = module.params['timeout'] + name = module.params['name'] + transport_type = module.params['type'] + status = module.params['status'] + max_sessions = module.params['max_sessions'] + max_attempts = module.params['max_attempts'] + attempt_interval = module.params['attempt_interval'] + # Script + script_name = module.params['script_name'] + script_params = module.params['script_params'] + # SMS + gsm_modem = module.params['gsm_modem'] + # Jabber + username = module.params['username'] + password = module.params['password'] + # Email + smtp_server = module.params['smtp_server'] + smtp_server_port = module.params['smtp_server_port'] + smtp_helo = module.params['smtp_helo'] + smtp_email = module.params['smtp_email'] + smtp_security = module.params['smtp_security'] + smtp_authentication = module.params['smtp_authentication'] + smtp_verify_host = module.params['smtp_verify_host'] + smtp_verify_peer = module.params['smtp_verify_peer'] + # EZ Text + message_text_limit = module.params['message_text_limit'] + + zbx = None + + # login to zabbix + try: + zbx = ZabbixAPI(server_url, timeout=timeout, user=http_login_user, passwd=http_login_password, + validate_certs=validate_certs) + zbx.login(login_user, login_password) + except Exception as e: + module.fail_json(msg="Failed to connect to Zabbix server: %s" % e) + + mediatype_exists, mediatype_id = check_if_mediatype_exists(module, zbx, name) + + parameters = construct_parameters( + name=name, + transport_type=transport_type, + status=status, + max_sessions=max_sessions, + max_attempts=max_attempts, + attempt_interval=attempt_interval, + script_name=script_name, + script_params=script_params, + gsm_modem=gsm_modem, + username=username, + password=password, + smtp_server=smtp_server, + smtp_server_port=smtp_server_port, + smtp_helo=smtp_helo, + smtp_email=smtp_email, + smtp_security=smtp_security, + smtp_authentication=smtp_authentication, + smtp_verify_host=smtp_verify_host, + smtp_verify_peer=smtp_verify_peer, + message_text_limit=message_text_limit + ) + + if mediatype_exists: + if state == 'absent': + if module.check_mode: + module.exit_json( + changed=True, + msg="Mediatype would have been deleted. Name: {name}, ID: {_id}".format( + name=name, + _id=mediatype_id + ) + ) + mediatype_id = delete_mediatype(module, zbx, mediatype_id) + module.exit_json( + changed=True, + msg="Mediatype deleted. Name: {name}, ID: {_id}".format( + name=name, + _id=mediatype_id + ) + ) + else: + params_to_update, diff = get_update_params(module, zbx, mediatype_id, **parameters) + if params_to_update == {}: + module.exit_json( + changed=False, + msg="Mediatype is up to date: {name}".format(name=name) + ) + else: + if module.check_mode: + module.exit_json( + changed=True, + diff=diff, + msg="Mediatype would have been updated. Name: {name}, ID: {_id}".format( + name=name, + _id=mediatype_id + ) + ) + mediatype_id = update_mediatype( + module, zbx, + mediatypeid=mediatype_id, + **params_to_update + ) + module.exit_json( + changed=True, + diff=diff, + msg="Mediatype updated. Name: {name}, ID: {_id}".format( + name=name, + _id=mediatype_id + ) + ) + else: + if state == "absent": + module.exit_json(changed=False) + else: + if module.check_mode: + module.exit_json( + changed=True, + msg="Mediatype would have been created. Name: {name}, ID: {_id}".format( + name=name, + _id=mediatype_id + ) + ) + mediatype_id = create_mediatype(module, zbx, **parameters) + module.exit_json( + changed=True, + msg="Mediatype created: {name}, ID: {_id}".format( + name=name, + _id=mediatype_id + ) + ) + + +if __name__ == '__main__': + main()