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:
parent
e957760d52
commit
c35a562345
1 changed files with 314 additions and 43 deletions
|
@ -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__':
|
||||
|
|
Loading…
Reference in a new issue