@ -16,13 +16,15 @@ ANSIBLE_METADATA = {'metadata_version': '1.1',
DOCUMENTATION = '''
- - -
module : zabbix_template
short_description : Create / delete/ dump Zabbix template
short_description : Create / update/ delete/ dump Zabbix template
description :
- Create / delete / dump Zabbix template .
- This module allows you to create , modify , delete and dump Zabbix templates .
- Multiple templates can be created or modified at once if passing JSON or XML to module .
version_added : " 2.5 "
author :
- " sookido (@sookido) "
- " Logan Vig (@logan2211) "
- " Dusan Matejka (@D3DeFi) "
requirements :
- " python >= 2.6 "
- " zabbix-api >= 0.5.3 "
@ -30,33 +32,75 @@ options:
template_name :
description :
- Name of Zabbix template .
- Required when I ( template_json ) is not used .
- Required when I ( template_json ) or I ( template_xml ) are not used .
- Mutually exclusive with I ( template_json ) and I ( template_xml ) .
required : false
template_json :
description :
- JSON dump of template to import .
- JSON dump of templates to import .
- Multiple templates can be imported this way .
- Mutually exclusive with I ( template_name ) and I ( template_xml ) .
required : false
type : json
template_xml :
description :
- XML dump of templates to import .
- Multiple templates can be imported this way .
- You are advised to pass XML structure matching the structure used by your version of Zabbix server .
- Custom XML structure can be imported as long as it is valid , but may not yield consistent idempotent
results on subsequent runs .
- Mutually exclusive with I ( template_name ) and I ( template_json ) .
required : false
version_added : ' 2.9 '
template_groups :
description :
- List of template groups to create or delete .
- Required when I ( template_name ) is used and C ( state = present ) .
- List of host groups to add template to when template is createad .
- Replaces the current host groups the template belongs to if the template is already present .
- Required when creating a new template with C ( state = present ) and I ( template_name ) is used .
Not required when updating an existing template .
required : false
type : list
link_templates :
description :
- List of templates linked to the template .
- List of template names to be linked to the template .
- Templates that are not specified and are linked to the existing template will be only unlinked and not
cleared from the template .
required : false
type : list
clear_templates :
description :
- List of template s cleared from the template .
- See templates_clear in https : / / www . zabbix . com / documentation / 3.0 / manual / api / reference / template / update
- List of template name s to be unlinked and cleared from the template .
- This option is ignored if template is being created for the first time .
required : false
type : list
macros :
description :
- List of template macros .
- List of user macros to create for the template .
- Macros that are not specified and are present on the existing template will be replaced .
- See examples on how to pass macros .
required : false
type : list
suboptions :
name :
description :
- Name of the macro .
- Must be specified in { $ NAME } format .
value :
description :
- Value of the macro .
dump_format :
description :
- Format to use when dumping template with C ( state = dump ) .
required : false
choices : [ json , xml ]
default : " json "
version_added : ' 2.9 '
state :
description :
- ' State: present - create/update template; absent - delete template '
- Required state of the template .
- On C ( state = present ) template will be created / imported or updated depending if it is already present .
- On C ( state = dump ) template content will get dumped into required format specified in I ( dump_format ) .
- On C ( state = absent ) template will be deleted .
required : false
choices : [ present , absent , dump ]
default : " present "
@ -67,24 +111,19 @@ extends_documentation_fragment:
EXAMPLES = '''
- - -
# Creates a new Zabbix template from linked template
- name : Create Zabbix template using linked template
- name : Create a new Zabbix template linked to groups , macros and templates
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
@ -94,26 +133,41 @@ EXAMPLES = '''
value : ' Example '
state : present
# Create a new template from a JSON config definition
- name : Import Zabbix JSON template configuration
- name : Unlink and clear templates from the existing Zabbix template
local_action :
module : zabbix_template
server_url : http : / / 127.0 .0 .1
login_user : username
login_password : password
template_name : ExampleHost
clear_templates :
- Example template3
- Example template4
state : present
- name : Import Zabbix templates from JSON
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
- name : Import Zabbix templates from XML
local_action :
module : zabbix_template
server_url : http : / / 127.0 .0 .1
login_user : username
login_password : password
template_xml : " {{ lookup( ' file ' , ' zabbix_apache2.json ' ) }} "
state : present
- name : Import Zabbix template from Ansible dict variable
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 '
@ -125,11 +179,9 @@ EXAMPLES = '''
- 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
- name : Configure macros on the existing Zabbix template
local_action :
module : zabbix_template
server_url : http : / / 127.0 .0 .1
@ -141,7 +193,6 @@ EXAMPLES = '''
value : ' Example '
state : present
# Remove a template
- name : Delete Zabbix template
local_action :
module : zabbix_template
@ -151,14 +202,24 @@ EXAMPLES = '''
template_name : Template
state : absent
# Export template JSON definition
- name : Dump Zabbix template
- name : Dump Zabbix template as JSON
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
- name : Dump Zabbix template as XML
local_action :
module : zabbix_template
server_url : http : / / 127.0 .0 .1
login_user : username
login_password : password
template_name : Template
dump_format : xml
state : dump
register : template_dump
'''
@ -191,15 +252,53 @@ template_json:
} ]
}
}
template_xml :
description : dump of the template in XML representation
returned : when state is dump and dump_format is xml
type : str
sample : | -
< ? xml version = " 1.0 " ? >
< zabbix_export >
< version > 4.2 < / version >
< date > 2019 - 07 - 12 T13 : 37 : 26 Z < / date >
< groups >
< group >
< name > Templates < / name >
< / group >
< / groups >
< templates >
< template >
< template > test < / template >
< name > Test Template < / name >
< description / >
< groups >
< group >
< name > Templates < / name >
< / group >
< / groups >
< applications / >
< items / >
< discovery_rules / >
< httptests / >
< macros / >
< templates / >
< screens / >
< tags / >
< / template >
< / templates >
< / zabbix_export >
'''
from distutils . version import LooseVersion
from ansible . module_utils . basic import AnsibleModule , missing_required_lib
from ansible . module_utils . _text import to_native
import atexit
import json
import traceback
import xml . etree . ElementTree as ET
from distutils . version import LooseVersion
from ansible . module_utils . basic import AnsibleModule , missing_required_lib
from ansible . module_utils . _text import to_native
try :
from zabbix_api import ZabbixAPI , ZabbixAPIException
@ -253,57 +352,86 @@ class Template(object):
template_ids . append ( template_id )
return template_ids
def add_template ( self , template_name , template_json , group_ids ,
child_template_ids , macros ) :
def add_template ( self , template_name , group_ids , link_template_ids , macros ) :
if self . _module . check_mode :
self . _module . exit_json ( changed = True )
if template_json :
self . import_template ( template_json , template_name )
else :
self . _zapi . template . create ( { ' host ' : template_name ,
' groups ' : group_ids ,
' templates ' : child_template_ids ,
' macros ' : macros } )
def update_template ( self , templateids , template_json ,
group_ids , child_template_ids ,
clear_template_ids , macros ,
existing_template_json = None ) :
self . _zapi . template . create ( { ' host ' : template_name , ' groups ' : group_ids , ' templates ' : link_template_ids ,
' macros ' : macros } )
def check_template_changed ( self , template_ids , template_groups , link_templates , clear_templates ,
template_macros , template_content , template_type ) :
""" Compares template parameters to already existing values if any are found.
template_json - JSON structures are compared as deep sorted dictonaries ,
template_xml - XML structures are compared as strings , but filtered and formatted first ,
If none above is used , all the other arguments are compared to their existing counterparts
retrieved from Zabbix API . """
changed = False
# Compare filtered and formatted XMLs strings for any changes. It is expected that provided
# XML has same structure as Zabbix uses (e.g. it was optimally exported via Zabbix GUI or API)
if template_content is not None and template_type == ' xml ' :
existing_template = self . dump_template ( template_ids , template_type = ' xml ' )
if self . filter_xml_template ( template_content ) != self . filter_xml_template ( existing_template ) :
changed = True
return changed
existing_template = self . dump_template ( template_ids , template_type = ' json ' )
# Compare JSON objects as deep sorted python dictonaries
if template_content is not None and template_type == ' json ' :
parsed_template_json = self . load_json_template ( template_content )
if self . diff_template ( parsed_template_json , existing_template ) :
changed = True
return changed
# If neither template_json or template_xml were used, user provided all parameters via module options
if template_groups is not None :
existing_groups = [ g [ ' name ' ] for g in existing_template [ ' zabbix_export ' ] [ ' groups ' ] ]
if set ( template_groups ) != set ( existing_groups ) :
changed = True
# Check if any new templates would be linked or any existing would be unlinked
exist_child_templates = [ t [ ' name ' ] for t in existing_template [ ' zabbix_export ' ] [ ' templates ' ] [ 0 ] [ ' templates ' ] ]
if link_templates is not None :
if set ( link_templates ) != set ( exist_child_templates ) :
changed = True
# Mark that there will be changes when at least one existing template will be unlinked
if clear_templates is not None :
for t in clear_templates :
if t in exist_child_templates :
changed = True
break
if template_macros is not None :
existing_macros = existing_template [ ' zabbix_export ' ] [ ' templates ' ] [ 0 ] [ ' macros ' ]
if template_macros != existing_macros :
changed = True
return changed
def update_template ( self , template_ids , group_ids , link_template_ids , clear_template_ids , template_macros ) :
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 link_template_ids is not None :
template_changes . update ( { ' templates ' : link_template_ids } )
if template_changes :
template_changes . update ( {
' templateid ' : templateids ,
' templates_clear ' : clear_template_ids
} )
self . _zapi . template . update ( template_changes )
if clear_template_ids is not None :
template_changes . update ( { ' templates_clear ' : clear_template_ids } )
if do_import :
self . import_template ( template_json ,
existing_template_json [ ' zabbix_export ' ] [ ' templates ' ] [ 0 ] [ ' template ' ] )
if template_macros is not None :
template_changes . update ( { ' macros ' : template_macros } )
return changed
if template_changes :
# If we got here we know that only one template was provided via template_name
template_changes . update ( { ' templateid ' : template_ids [ 0 ] } )
self . _zapi . template . update ( template_changes )
def delete_template ( self , templateids ) :
if self . _module . check_mode :
@ -319,15 +447,17 @@ class Template(object):
else :
return obj
def dump_template ( self , template_ids ):
def dump_template ( self , template_ids , template_type = ' json ' ):
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 )
dump = self . _zapi . configuration . export ( { ' format ' : template_type , ' options ' : { ' templates ' : template_ids } } )
if template_type == ' xml ' :
return str ( ET . tostring ( ET . fromstring ( dump . encode ( ' utf-8 ' ) ) , encoding = ' utf-8 ' ) )
else :
return self . load_json_template ( dump )
except ZabbixAPIException as e :
self . _module . fail_json ( msg = ' Unable to export template: %s ' % e )
@ -359,21 +489,43 @@ class Template(object):
return template_json
def filter_xml_template ( self , template_xml ) :
""" Filters out keys from XML template that may wary between exports (e.g date or version) and
keys that are not imported via this module .
It is advised that provided XML template exactly matches XML structure used by Zabbix """
# Strip last new line and convert string to ElementTree
parsed_xml_root = self . load_xml_template ( template_xml . strip ( ) )
keep_keys = [ ' graphs ' , ' templates ' , ' triggers ' , ' value_maps ' ]
# Remove unwanted XML nodes
for node in list ( parsed_xml_root ) :
if node . tag not in keep_keys :
parsed_xml_root . remove ( node )
# Filter empty attributes from template objects to allow accurate comparison
for template in list ( parsed_xml_root . find ( ' templates ' ) ) :
for element in list ( template ) :
if element . text is None and len ( list ( element ) ) == 0 :
template . remove ( element )
# Filter new lines and identation
xml_root_text = list ( line . strip ( ) for line in ET . tostring ( parsed_xml_root ) . split ( ' \n ' ) )
return ' ' . join ( xml_root_text )
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 ( )
)
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 ' )
def load_xml_template ( self , template_xml ) :
try :
return ET . fromstring ( template_xml )
except ET . ParseError as e :
self . _module . fail_json ( msg = ' Invalid XML provided ' , details = to_native ( e ) , exception = traceback . format_exc ( ) )
def import_template ( self , template_content , template_type = ' json ' ) :
# rules schema latest version
update_rules = {
' applications ' : {
@ -427,21 +579,14 @@ class Template(object):
# 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 :
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
} )
import_data = { ' format ' : template_type , ' source ' : template_content , ' rules ' : update_rules }
self . _zapi . configuration . import_ ( import_data )
except ZabbixAPIException as e :
self . _module . fail_json (
msg = ' Unable to import JSON template ' ,
details = to_native ( e ) ,
exception = traceback . format_exc ( )
)
self . _module . fail_json ( msg = ' Unable to import template ' , details = to_native ( e ) ,
exception = traceback . format_exc ( ) )
def main ( ) :
@ -451,20 +596,29 @@ def main():
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 ) ,
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 = False ) ,
template_json = dict ( type = ' json ' , required = False ) ,
template_xml = dict ( type = ' str ' , 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 ' ] ) ,
dump_format= dict ( type = ' str ' , required = False , default = ' json ' , choices = [ ' json ' , ' xml ' ] ) ,
state = dict ( default = " present " , choices = [ ' present ' , ' absent ' , ' dump ' ] ) ,
timeout = dict ( type = ' int ' , default = 10 )
) ,
required_one_of = [ [ ' template_name ' , ' template_json ' ] ] ,
required_one_of = [
[ ' template_name ' , ' template_json ' , ' template_xml ' ]
] ,
mutually_exclusive = [
[ ' template_name ' , ' template_json ' , ' template_xml ' ]
] ,
required_if = [
[ ' state ' , ' absent ' , [ ' template_name ' ] ] ,
[ ' state ' , ' dump ' , [ ' template_name ' ] ]
] ,
supports_check_mode = True
)
@ -479,104 +633,110 @@ def main():
validate_certs = module . params [ ' validate_certs ' ]
template_name = module . params [ ' template_name ' ]
template_json = module . params [ ' template_json ' ]
template_xml = module . params [ ' template_xml ' ]
template_groups = module . params [ ' template_groups ' ]
link_templates = module . params [ ' link_templates ' ]
clear_templates = module . params [ ' clear_templates ' ]
template_macros = module . params [ ' macros ' ]
dump_format = module . params [ ' dump_format ' ]
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 = ZabbixAPI ( server_url , timeout = timeout , user = http_login_user , passwd = http_login_password ,
validate_certs= validate_certs )
zbx . login ( login_user , login_password )
atexit . register ( zbx . logout )
except ZabbixAPIException as e :
module . fail_json ( msg = " Failed to connect to Zabbix server: %s " % e )
template = Template ( module , zbx )
if template_json and not template_name :
# Ensure template_name is not empty for safety check in import_template
template_loaded = template . load_json_template ( template_json )
template_name = template_loaded [ ' zabbix_export ' ] [ ' templates ' ] [ 0 ] [ ' template ' ]
elif template_name and not template_groups and state == ' present ' :
module . fail_json ( msg = " Option template_groups is required when template_json is not used " )
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
# Identify template names for IDs retrieval
# Template names are expected to reside in ['zabbix_export']['templates'][*]['template'] for both data types
template_content , template_type = None , None
if template_json is not None :
template_type = ' json '
template_content = template_json
json_parsed = template . load_json_template ( template_content )
template_names = list ( t [ ' template ' ] for t in json_parsed [ ' zabbix_export ' ] [ ' templates ' ] )
elif template_xml is not None :
template_type = ' xml '
template_content = template_xml
xml_parsed = template . load_xml_template ( template_content )
template_names = list ( t . find ( ' template ' ) . text for t in list ( xml_parsed . find ( ' templates ' ) ) )
else :
template_names = [ template_name ]
template_ids = template . get_template_ids ( template_names )
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 )
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 )
module . exit_json ( changed = True , result = " Successfully deleted 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 )
if dump_format == ' json ' :
module . exit_json ( changed = False , template_json = template . dump_template ( template_ids , template_type = ' json ' ) )
elif dump_format == ' xml ' :
module . exit_json ( changed = False , template_xml = template . dump_template ( template_ids , template_type = ' xml ' ) )
elif state == " present " :
child_template_ids = None
# Load all subelements for template that were provided by user
group_ids = None
if template_groups is not None :
group_ids = template . get_group_ids_by_group_names ( template_groups )
link_template_ids = None
if link_templates is not None :
existing_child_templates = None
if existing_template_json :
existing_child_templates = set ( list (
tmpl [ ' name ' ] for tmpl in existing_template_json [ ' zabbix_export ' ] [ ' templates ' ] [ 0 ] [ ' templates ' ] ) )
if not existing_template_json or set ( link_templates ) != existing_child_templates :
child_template_ids = template . get_template_ids ( link_templates )
clear_template_ids = [ ]
link_template_ids = template . get_template_ids ( link_templates )
clear_template_ids = None
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
# zabbix configuration.export does not differentiate python types (numbers are returned as strings)
# Zabbix configuration.export does not differentiate python types (numbers are returned as strings)
for macroitem in template_macros :
for key in macroitem :
macroitem [ key ] = str ( macroitem [ key ] )
if existing_template_json :
existing_macros = existing_template_json [ ' zabbix_export ' ] [ ' templates ' ] [ 0 ] [ ' macros ' ]
if not existing_macros or 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 )
# Assume new templates are being added when no ID's were found
if template_content is not None :
template . import_template ( template_content , template_type )
module . exit_json ( changed = True , result = " Template import successful " )
else :
if group_ids is None :
module . fail_json ( msg = ' template_groups are required when creating a new Zabbix template ' )
template . add_template ( template_name , group_ids , link_template_ids , template_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 updated template: %s " %
template_name )
changed = template . check_template_changed ( template_ids , template_groups , link_templates , clear_templates ,
template_macros , template_content , template_type )
if module . check_mode :
module . exit_json ( changed = changed )
if changed :
if template_type is not None :
template . import_template ( template_content , template_type )
else :
template . update_template ( template_ids , group_ids , link_template_ids , clear_template_ids ,
template_macros )
module . exit_json ( changed = changed , result = " Template successfully updated " )
if __name__ == ' __main__ ' :