#!/usr/bin/python # -*- coding: utf-8 -*- # (c) 2017, sookido # # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . from __future__ import absolute_import, division, print_function __metaclass__ = type ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ['preview'], 'supported_by': 'community'} DOCUMENTATION = ''' module: zabbix_template short_description: create/delete/dump zabbix template description: - create/delete/dump zabbix template version_added: "2.5" author: - "@sookido" - "Logan Vig (@logan2211)" requirements: - "python >= 2.6" - "zabbix-api >= 0.5.3" options: template_name: description: - Name of zabbix template required: true template_json: description: - JSON dump of template to import required: false template_groups: description: - List of template groups to create or delete. required: false link_templates: description: - List of templates linked to the template. required: false clear_templates: description: - List of templates cleared from the template. - see templates_clear in https://www.zabbix.com/documentation/3.0/manual/api/reference/template/update required: false macros: description: - List of templates macro required: false state: description: - state present create/update template, absent delete template required: false choices: [present, absent, dump] default: "present" extends_documentation_fragment: - zabbix ''' EXAMPLES = ''' --- # Creates a new zabbix template from linked template - name: Create Zabbix template using linked template local_action: module: zabbix_template server_url: http://127.0.0.1 login_user: username login_password: password template_name: ExampleHost template_json: "{'zabbix_export': {}}" template_groups: - Role - Role2 link_templates: - Example template1 - Example template2 clear_templates: - Example template3 - Example template4 macros: - macro: '{$EXAMPLE_MACRO1}' value: 30000 - macro: '{$EXAMPLE_MACRO2}' value: 3 - macro: '{$EXAMPLE_MACRO3}' value: 'Example' state: present # Create a new template from a json config definition - name: Import Zabbix json template configuration local_action: module: zabbix_template server_url: http://127.0.0.1 login_user: username login_password: password template_name: Apache2 template_json: "{{ lookup('file', 'zabbix_apache2.json') }}" template_groups: - Webservers state: present # Import a template from Ansible variable dict - name: Import Zabbix Template zabbix_template: login_user: username login_password: password server_url: http://127.0.0.1 template_name: Test Template template_json: zabbix_export: version: '3.2' templates: - name: Template for Testing description: 'Testing template import' template: Test Template groups: - name: Templates applications: - name: Test Application template_groups: Templates state: present # Add a macro to a template - name: Set a macro on the Zabbix template local_action: module: zabbix_template server_url: http://127.0.0.1 login_user: username login_password: password template_name: Template macros: - macro: '{$TEST_MACRO}' value: 'Example' state: present # Remove a template - name: Delete Zabbix template local_action: module: zabbix_template server_url: http://127.0.0.1 login_user: username login_password: password template_name: Template state: absent # Export template json definition - name: Dump Zabbix template local_action: module: zabbix_template server_url: http://127.0.0.1 login_user: username login_password: password template_name: Template state: dump register: template_dump ''' RETURN = ''' template_json: description: The JSON dump of the template returned: when state is dump type: string sample: { "zabbix_export":{ "date":"2017-11-29T16:37:24Z", "templates":[{ "templates":[], "description":"", "httptests":[], "screens":[], "applications":[], "discovery_rules":[], "groups":[{"name":"Templates"}], "name":"Test Template", "items":[], "macros":[], "template":"test" }], "version":"3.2", "groups":[{ "name":"Templates" }] } } ''' from distutils.version import LooseVersion from ansible.module_utils.basic import AnsibleModule from ansible.module_utils._text import to_native import json import traceback try: from zabbix_api import ZabbixAPI, ZabbixAPIException HAS_ZABBIX_API = True except ImportError: HAS_ZABBIX_API = False class Template(object): def __init__(self, module, zbx): self._module = module self._zapi = zbx # check if host group exists def check_host_group_exist(self, group_names): for group_name in group_names: result = self._zapi.hostgroup.get({'filter': {'name': group_name}}) if not result: self._module.fail_json(msg="Hostgroup not found: %s" % group_name) return True # get group ids by group names def get_group_ids_by_group_names(self, group_names): group_ids = [] if group_names is None or len(group_names) == 0: return group_ids if self.check_host_group_exist(group_names): group_list = self._zapi.hostgroup.get( {'output': 'extend', 'filter': {'name': group_names}}) for group in group_list: group_id = group['groupid'] group_ids.append({'groupid': group_id}) return group_ids def get_template_ids(self, template_list): template_ids = [] if template_list is None or len(template_list) == 0: return template_ids for template in template_list: template_list = self._zapi.template.get( {'output': 'extend', 'filter': {'host': template}}) if len(template_list) < 1: continue else: template_id = template_list[0]['templateid'] template_ids.append(template_id) return template_ids def add_template(self, template_name, template_json, group_ids, child_template_ids, macros): if self._module.check_mode: self._module.exit_json(changed=True) self._zapi.template.create({'host': template_name, 'groups': group_ids, 'templates': child_template_ids, 'macros': macros}) if template_json: self.import_template(template_json, template_name) def update_template(self, templateids, template_json, group_ids, child_template_ids, clear_template_ids, macros, existing_template_json=None): changed = False template_changes = {} if group_ids is not None: template_changes.update({'groups': group_ids}) changed = True if child_template_ids is not None: template_changes.update({'templates': child_template_ids}) changed = True if macros is not None: template_changes.update({'macros': macros}) changed = True do_import = False if template_json: parsed_template_json = self.load_json_template(template_json) if self.diff_template(parsed_template_json, existing_template_json): do_import = True changed = True if self._module.check_mode: self._module.exit_json(changed=changed) if template_changes: template_changes.update({ 'templateid': templateids, 'templates_clear': clear_template_ids }) self._zapi.template.update(template_changes) if do_import: self.import_template(template_json, existing_template_json['zabbix_export']['templates'][0]['template']) return changed def delete_template(self, templateids): if self._module.check_mode: self._module.exit_json(changed=True) self._zapi.template.delete(templateids) def ordered_json(self, obj): # Deep sort json dicts for comparison if isinstance(obj, dict): return sorted((k, self.ordered_json(v)) for k, v in obj.items()) if isinstance(obj, list): return sorted(self.ordered_json(x) for x in obj) else: return obj def dump_template(self, template_ids): if self._module.check_mode: self._module.exit_json(changed=True) try: dump = self._zapi.configuration.export({ 'format': 'json', 'options': {'templates': template_ids} }) return self.load_json_template(dump) except ZabbixAPIException as e: self._module.fail_json(msg='Unable to export template: %s' % e) def diff_template(self, template_json_a, template_json_b): # Compare 2 zabbix templates and return True if they differ. template_json_a = self.filter_template(template_json_a) template_json_b = self.filter_template(template_json_b) if self.ordered_json(template_json_a) == self.ordered_json(template_json_b): return False return True def filter_template(self, template_json): # Filter the template json to contain only the keys we will update keep_keys = set(['graphs', 'templates', 'triggers', 'value_maps']) unwanted_keys = set(template_json['zabbix_export']) - keep_keys for unwanted_key in unwanted_keys: del template_json['zabbix_export'][unwanted_key] return template_json def load_json_template(self, template_json): try: return json.loads(template_json) except ValueError as e: self._module.fail_json( msg='Invalid JSON provided', details=to_native(e), exception=traceback.format_exc() ) def import_template(self, template_json, template_name=None): parsed_template_json = self.load_json_template(template_json) if template_name != parsed_template_json['zabbix_export']['templates'][0]['template']: self._module.fail_json(msg='JSON template name does not match presented name') # rules schema latest version update_rules = { 'applications': { 'createMissing': True, 'deleteMissing': True }, 'discoveryRules': { 'createMissing': True, 'updateExisting': True, 'deleteMissing': True }, 'graphs': { 'createMissing': True, 'updateExisting': True, 'deleteMissing': True }, 'httptests': { 'createMissing': True, 'updateExisting': True, 'deleteMissing': True }, 'items': { 'createMissing': True, 'updateExisting': True, 'deleteMissing': True }, 'templates': { 'createMissing': True, 'updateExisting': True }, 'templateScreens': { 'createMissing': True, 'updateExisting': True, 'deleteMissing': True }, 'triggers': { 'createMissing': True, 'updateExisting': True, 'deleteMissing': True }, 'valueMaps': { 'createMissing': True, 'updateExisting': True } } try: # old api version support here api_version = self._zapi.api_version() # updateExisting for application removed from zabbix api after 3.2 if LooseVersion(api_version).version[:2] <= LooseVersion( '3.2').version: update_rules['applications']['updateExisting'] = True self._zapi.configuration.import_({ 'format': 'json', 'source': template_json, 'rules': update_rules }) except ZabbixAPIException as e: self._module.fail_json( msg='Unable to import JSON template', details=to_native(e), exception=traceback.format_exc() ) def main(): module = AnsibleModule( 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), template_name=dict(type='str', required=True), template_json=dict(type='json', required=False), template_groups=dict(type='list', required=False), link_templates=dict(type='list', required=False), clear_templates=dict(type='list', required=False), macros=dict(type='list', required=False), state=dict(default="present", choices=['present', 'absent', 'dump']), timeout=dict(type='int', default=10) ), supports_check_mode=True ) if not HAS_ZABBIX_API: module.fail_json(msg="Missing required zabbix-api module " + "(check docs or install with: " + "pip install zabbix-api)") 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'] template_name = module.params['template_name'] template_json = module.params['template_json'] template_groups = module.params['template_groups'] link_templates = module.params['link_templates'] clear_templates = module.params['clear_templates'] template_macros = module.params['macros'] state = module.params['state'] timeout = module.params['timeout'] 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 ZabbixAPIException as e: module.fail_json(msg="Failed to connect to Zabbix server: %s" % e) template = Template(module, zbx) template_ids = template.get_template_ids([template_name]) existing_template_json = None if template_ids: existing_template_json = template.dump_template(template_ids) # delete template if state == "absent": # if template not found. no change, no fail if not template_ids: module.exit_json(changed=False, msg="Template not found. " + "No changed: %s" % template_name) template.delete_template(template_ids) module.exit_json(changed=True, result="Successfully delete template %s" % template_name) elif state == "dump": if not template_ids: module.fail_json(msg='Template not found: %s' % template_name) module.exit_json(changed=False, template_json=existing_template_json) elif state == "present": child_template_ids = None if link_templates is not None: child_template_ids = template.get_template_ids(link_templates) clear_template_ids = [] if clear_templates is not None: clear_template_ids = template.get_template_ids(clear_templates) group_ids = None if template_groups is not None: # If the template exists, compare the already set groups existing_groups = None if existing_template_json: existing_groups = set(list(group['name'] for group in existing_template_json['zabbix_export']['groups'])) if not existing_groups or set(template_groups) != existing_groups: group_ids = template.get_group_ids_by_group_names(template_groups) macros = None if template_macros is not None: existing_macros = None if existing_template_json: existing_macros = set(existing_template_json['zabbix_export']['templates'][0]['macros']) if not existing_macros or set(template_macros) != existing_macros: macros = template_macros if not template_ids: template.add_template(template_name, template_json, group_ids, child_template_ids, macros) module.exit_json(changed=True, result="Successfully added template: %s" % template_name) else: changed = template.update_template(template_ids[0], template_json, group_ids, child_template_ids, clear_template_ids, macros, existing_template_json) module.exit_json(changed=changed, result="Successfully updateed template: %s" % template_name) if __name__ == '__main__': main()