Skip to content

Commit 7601dad

Browse files
committed
Added new rules
1 parent 3558553 commit 7601dad

File tree

1 file changed

+162
-41
lines changed

1 file changed

+162
-41
lines changed

zabbix-template-convertor

Lines changed: 162 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/bin/python
22
import xml.etree.ElementTree as ET
33
import 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

131140
class 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
181301
doc = 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"?>')
198319
ET.dump(doc)

0 commit comments

Comments
 (0)