11#!/usr/bin/python
22import xml .etree .ElementTree as ET
33import argparse
4+ import re
45
56__version__ = '1.1.0'
67
@@ -53,7 +54,11 @@ class ConversionRule(object):
5354 self .version = version
5455
5556 def __str__ (self ):
56- return "Base conversion rule class"
57+ """
58+ Return a string representation describing the conversion rule.
59+ """
60+
61+ raise NotImplementedError ('__str__ method not implemented for %s' % self .__class__ .__name__ )
5762
5863 def apply (self ):
5964 """
@@ -64,7 +69,11 @@ class ConversionRule(object):
6469 raise NotImplementedError ('apply method not implemented for %s' % self .__class__ .__name__ )
6570
6671 def versioncmp (self , version ):
67- """Compare two Zabbix version strings"""
72+ """
73+ Compare two Zabbix version strings. Returns zero if the given version
74+ matches the desired output version. Returns negative if the given
75+ version is older and returns positive if the given version is newer.
76+ """
6877
6978 from pkg_resources import parse_version as V
7079 return cmp (V (self .version ), V (version ))
@@ -114,68 +123,179 @@ class ValueMapsMustNotBeExported(ConversionRule):
114123 if node is not None :
115124 root .remove (node )
116125
117- class ValueMapsMustNotBeReferenced (ConversionRule ):
126+ class FixMissingValueMaps (ConversionRule ):
118127 """Rule to remove references to value maps"""
119128
120129 def __str__ (self ):
121- return 'Value maps must not be used if value map squashing is enabled before 3.0.0 '
130+ return 'Value maps cannot be used before 3.0.0 if value map squashing is enabled'
122131
123132 def apply (self ):
124133 if not args .squash_value_maps or self .versioncmp ('3' ) >= 0 :
125134 raise NotApplicableError ()
126135
127- for itemtype in [ 'item' , 'item_prototype' ] :
136+ for itemtype in ( 'item' , 'item_prototype' ) :
128137 for node in self .root .findall ('.//%s/valuemap' % itemtype ):
129138 node .clear ()
130139
131140class FixDiscoveryRuleFilters (ConversionRule ):
132141 """
133- Rule to ensure discovery rule filters comply with the new format introduced
134- in 2.3.0 via ZBXNEXT-581
142+ Rule to ensure discovery rule filters use a single expression before 2.3.0.
143+ See: ZBXNEXT-581
135144 """
136145
137146 def __str__ (self ):
138- return 'Discovery rule filters must use a single expression until 2.4 .0'
147+ return 'Discovery rule filters must use a single expression before 2.3 .0'
139148
140149 def apply (self ):
141- if self .versioncmp ('2.4.0' ) < 0 :
142- # try convert a multi-filter into a single filter string
143- for discovery_rule in root .findall ('.//discovery_rule' ):
144- discovery_rule_name = discovery_rule .find ('name' ).text
145- node = discovery_rule .find ('filter' )
150+ if self .versioncmp ('2.3.0' ) >= 0 :
151+ raise NotApplicableError ()
146152
147- # check if multi-filter can be downgraded to filter expression
148- if (
149- node .find ('evaltype' ).text == '0'
150- and node .find ('formula' ).text is None
151- ):
152- conditions = node .find ('conditions' )
153-
154- if len (conditions ) == 0 :
155- # default blank filter
156- node .clear ()
153+ # try convert a multi-filter into a single filter string
154+ for discovery_rule in root .findall ('.//discovery_rule' ):
155+ discovery_rule_name = discovery_rule .find ('name' ).text
156+ node = discovery_rule .find ('filter' )
157+
158+ # check if multi-filter can be downgraded to filter expression
159+ if (
160+ node .find ('evaltype' ).text == '0'
161+ and node .find ('formula' ).text is None
162+ ):
163+ conditions = node .find ('conditions' )
164+
165+ if len (conditions ) == 0 :
166+ # default blank filter
167+ node .clear ()
168+
169+ elif len (conditions ) > 1 :
170+ # multi-filter has too many conditions
171+ warn ('filter for discovery rule \' %s\' has multiple conditions and cannot be converted - dropping' % discovery_rule_name )
172+ node .clear ()
157173
158- elif len (conditions ) > 1 :
159- # multi-filter has too many conditions
160- warn ('filter for discovery rule \' %s\' has multiple conditions and cannot be converted - dropping' % discovery_rule_name )
174+ else :
175+ condition = conditions [0 ]
176+ if (
177+ condition .findtext ('.//operator' ) == '8'
178+ and condition .findtext ('.//formulaid' ) == 'A'
179+ ):
180+ # convert to filter expression
181+ filter_str = '%s:%s' % (condition .findtext ('macro' ), condition .findtext ('value' ))
161182 node .clear ()
162-
183+ node . text = filter_str
163184 else :
164- condition = conditions [0 ]
165- if (
166- condition .findtext ('.//operator' ) == '8'
167- and condition .findtext ('.//formulaid' ) == 'A'
168- ):
169- # convert to filter expression
170- filter_str = '%s:%s' % (condition .findtext ('macro' ), condition .findtext ('value' ))
171- node .clear ()
172- node .text = filter_str
173- else :
174- # unsupported operator or formula id
175- warn ('filter condition for discovery rule \' %s\' uses an operator or formula ID that cannot be converted - dropping' % discovery_rule_name )
176- node .clear ()
185+ # unsupported operator or formula id
186+ warn ('filter condition for discovery rule \' %s\' uses an operator or formula ID that cannot be converted - dropping' % discovery_rule_name )
187+ node .clear ()
188+ else :
189+ raise ValueError ('malformed discovery rule filter in \' %s\' ' % discovery_rule_name )
190+
191+ class FixApplicationPrototypes (ConversionRule ):
192+ """
193+ Rule to ensure application prototypes are not exported with discovery item
194+ prototypes before 2.5.0.
195+ See: ZBXNEXT-1219
196+ """
197+
198+ def __str__ (self ):
199+ return 'Application prototypes cannot be used before 2.5.0'
200+
201+ def apply (self ):
202+ if self .versioncmp ('2.5.0' ) >= 0 :
203+ raise NotApplicableError ()
204+
205+ for node in self .root .findall ('.//item_prototype' ):
206+ aps = node .find ('application_prototypes' )
207+ if aps is not None :
208+ node .remove (aps )
209+
210+ class FixSNMPDiscovery (ConversionRule ):
211+ """
212+ Rule to ensure the OID for SNMP discovery rules use a single OID before
213+ 2.5.0.
214+ See: ZBXNEXT-1554
215+ """
216+
217+ def __str__ (self ):
218+ return 'SNMP Discovery rules must use a single OID before 2.5.0'
219+
220+ def apply (self ):
221+ if self .versioncmp ('2.5.0' ) >= 0 :
222+ raise NotApplicableError ()
223+
224+ for discovery_rule in self .root .findall ('.//discovery_rule' ):
225+ node = discovery_rule .find ('snmp_oid' )
226+ if (
227+ node is not None
228+ and node .text is not None
229+ and node .text .startswith ('discovery[' )
230+ ):
231+ m = re .search (r"^discovery\[(.*?,)([^,\]]+)" , node .text )
232+ if m is not None and len (m .groups ()) == 2 :
233+ node .text = m .group (2 )
234+ if node .text .startswith ('disc' ):
235+ warn (node .text )
177236 else :
178- raise ValueError ('malformed discovery rule filter in \' %s\' ' % discovery_rule_name )
237+ raise ValueError ('Unrecognised SNMP OID for discovery rule \' %s\' ' % discovery_rule .findtext ('name' ))
238+
239+ class FixTriggerPrototypesDependencies (ConversionRule ):
240+ """
241+ Rule to ensure trigger prototype dependencies are not exported before 2.5.0.
242+ See: ZBXNEXT-1229
243+ """
244+
245+ def __str__ (self ):
246+ return 'Trigger prototype dependencies cannot be used before 2.5.0'
247+
248+ def apply (self ):
249+ if self .versioncmp ('2.5.0' ) >= 0 :
250+ raise NotApplicableError ()
251+
252+ for trigger_prototype in self .root .findall ('.//trigger_prototype' ):
253+ node = trigger_prototype .find ('dependencies' )
254+ if node is not None :
255+ trigger_prototype .remove (node )
256+
257+ class FixTriggerOperators (ConversionRule ):
258+ """
259+ Rule to ensure logical operators in trigger expressions s use ampersand (&)
260+ and pipe (|) instead of 'and' and 'or' before 2.4.
261+ """
262+
263+ def __str__ (self ):
264+ return 'Logical operators in triggers must use ampersand and pipe before 2.4.0'
265+
266+ def apply (self ):
267+ if self .versioncmp ('2.4' ) >= 0 :
268+ raise NotApplicableError ()
269+
270+ for triggertype in ( 'trigger' , 'trigger_prototype' ):
271+ for trigger in self .root .findall ('.//%s' % triggertype ):
272+ node = trigger .find ('expression' )
273+ if (
274+ node is not None
275+ and node .text is not None
276+ ):
277+ node .text = re .sub (r"\s+and\s+" , '&' , node .text )
278+ node .text = re .sub (r"\s+or\s+" , '|' , node .text )
279+
280+ class FixLastTriggerFunction (ConversionRule ):
281+ """
282+ Rule to ensure a default parameter value is specified when calling the
283+ last() trigger expression before 2.2.
284+ """
285+
286+ def __str__ (self ):
287+ return 'Trigger function \' last()\' must include a parameter'
288+
289+ def apply (self ):
290+ if self .versioncmp ('2.2' ) >= 0 :
291+ raise NotApplicableError ()
292+
293+ for node in self .root .findall ('.//expression' ):
294+ if (
295+ node is not None
296+ and node .text is not None
297+ ):
298+ node .text = re .sub (r"\.last\(\)" , '.last(#1)' , node .text )
179299
180300# read xml template
181301doc = ET .parse (args .file )
@@ -195,4 +315,5 @@ for rule in rules:
195315 pass
196316
197317# print modified template
318+ print ('<?xml version="1.0" encoding="UTF-8"?>' )
198319ET .dump (doc )
0 commit comments