Add idempotency and import/export support to zabbix_template (#33362)

* Add idempotency and import/export support to zabbix_template

Adds idempotency to the template update functions and check mode,
also adds the ability to dump and import json template
configurations.

* Fix issue clearing groups from template

When an empty list is provided for group names, all groups associations
should be cleared from the template. Previous behavior caused the
template to be associated to all existing groups if an empty list
was provided.

* Fix undefined variable references

* Add example importing template from ansible variable

Document a sample template import with bare minimum structure.
No items or graphs are added, only 1 application is added to the
template.
This commit is contained in:
Logan V 2017-11-30 14:49:12 -06:00 committed by ansibot
parent e957760d52
commit c35a562345

View file

@ -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__':