diff --git a/lib/ansible/modules/monitoring/zabbix/zabbix_template.py b/lib/ansible/modules/monitoring/zabbix/zabbix_template.py index 7946f76ccc5..775bb7f75be 100644 --- a/lib/ansible/modules/monitoring/zabbix/zabbix_template.py +++ b/lib/ansible/modules/monitoring/zabbix/zabbix_template.py @@ -28,12 +28,13 @@ ANSIBLE_METADATA = {'metadata_version': '1.1', DOCUMENTATION = ''' module: zabbix_template -short_description: create/delete zabbix template +short_description: create/delete/dump zabbix template description: - - create/delete zabbix template + - create/delete/dump zabbix template version_added: "2.5" author: - "@sookido" + - "Logan Vig (@logan2211)" requirements: - "python >= 2.6" - "zabbix-api >= 0.5.3" @@ -42,10 +43,14 @@ options: 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: true + required: false link_templates: description: - List of templates linked to the template. @@ -63,7 +68,7 @@ options: description: - state present create/update template, absent delete template required: false - choices: [ present, absent] + choices: [present, absent, dump] default: "present" extends_documentation_fragment: @@ -72,13 +77,15 @@ extends_documentation_fragment: EXAMPLES = ''' --- -- name: create templates +# 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 @@ -96,16 +103,114 @@ EXAMPLES = ''' - 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 = ''' -#defaults +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 ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +import json +import traceback + + try: - from zabbix_api import ZabbixAPI + from zabbix_api import ZabbixAPI, ZabbixAPIException HAS_ZABBIX_API = True except ImportError: @@ -129,6 +234,8 @@ class Template(object): # 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', @@ -153,7 +260,7 @@ class Template(object): template_ids.append(template_id) return template_ids - def add_template(self, template_name, group_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) @@ -161,23 +268,162 @@ class Template(object): 'groups': group_ids, 'templates': child_template_ids, 'macros': macros}) + if template_json: + self.import_template(template_json, template_name) - def update_template(self, templateids, + def update_template(self, templateids, template_json, group_ids, child_template_ids, - clear_template_ids, macros): + 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=True) - self._zapi.template.update( - {'templateid': templateids, 'groups': group_ids, - 'templates': child_template_ids, - 'templates_clear': clear_template_ids, - 'macros': macros}) + 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') + + try: + self._zapi.configuration.import_({ + 'format': 'json', + 'source': template_json, + 'rules': { + 'applications': { + 'createMissing': True, + 'updateExisting': 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 + } + } + }) + 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( @@ -190,11 +436,13 @@ def main(): default=None, no_log=True), validate_certs=dict(type='bool', required=False, default=True), template_name=dict(type='str', required=True), - template_groups=dict(type='list', 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']), + state=dict(default="present", choices=['present', 'absent', + 'dump']), timeout=dict(type='int', default=10) ), supports_check_mode=True @@ -212,10 +460,11 @@ def main(): 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'] - macros = module.params['macros'] + template_macros = module.params['macros'] state = module.params['state'] timeout = module.params['timeout'] @@ -226,11 +475,14 @@ def main(): 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: + 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": @@ -244,32 +496,51 @@ def main(): result="Successfully delete template %s" % template_name) - child_template_ids = [] - if link_templates: - child_template_ids = template.get_template_ids(link_templates) + 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) - clear_template_ids = [] - if clear_templates: - clear_template_ids = template.get_template_ids(clear_templates) + elif state == "present": + child_template_ids = None + if link_templates is not None: + child_template_ids = template.get_template_ids(link_templates) - group_ids = template.get_group_ids_by_group_names(template_groups) - if not group_ids: - module.fail_json(msg='Template groups not found: %s' % - str(template_groups)) + clear_template_ids = [] + if clear_templates is not None: + clear_template_ids = template.get_template_ids(clear_templates) - if not template_ids: - template.add_template(template_name, group_ids, - child_template_ids, macros) - module.exit_json(changed=True, - result="Successfully added template: %s" % - template_name) - else: - template.update_template(template_ids[0], group_ids, - child_template_ids, clear_template_ids, - macros) - module.exit_json(changed=True, - result="Successfully updateed template: %s" % - template_name) + 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__':