4
4
"""
5
5
import sys
6
6
import os
7
+ import pathlib
7
8
import datetime
8
9
from dataclasses import dataclass
9
10
from typing import List , Union
10
11
11
12
import time
12
13
import threading
13
14
import logging
15
+
16
+ import requests
14
17
import yaml
15
18
import pytz
16
19
20
23
21
24
__author__ = 'Artem Aleksandrov <qk4l()tem4uk.ru>'
22
25
__license__ = """The MIT License (MIT)"""
23
- __version__ = '2.0.0'
26
+ __version__ = '2.1.1'
27
+
28
+
29
+ class Config :
30
+ _instance = None
31
+
32
+ def __new__ (cls , * args , ** kwargs ):
33
+ if cls ._instance is None :
34
+ cls ._instance = super (Config , cls ).__new__ (cls , * args , ** kwargs )
35
+ return cls ._instance
36
+
37
+ def __init__ (self ):
38
+ if not hasattr (self , 'initialized' ):
39
+ if os .getenv ('CONFIG_FILE' ) is not None :
40
+ self .config_file = pathlib .Path (os .environ ['CONFIG_FILE' ])
41
+ else :
42
+ self .config_file = pathlib .Path .cwd () / 'config.yml'
43
+ if not self .config_file .is_file ():
44
+ logging .error (
45
+ f"Config file { self .config_file } is absent. Set CONFIG_FILE to change path or create it there." )
46
+ sys .exit (1 )
47
+ config = read_config (self .config_file )
48
+ if not config :
49
+ sys .exit (1 )
50
+ self .zabbix_config = config ['zabbix' ]
51
+ self .cachet_config = config ['cachet' ]
52
+ self .app_settings = config ['settings' ]
53
+
54
+ if self .app_settings .get ('time_zone' ):
55
+ self .tz = pytz .timezone (self .app_settings ['time_zone' ])
56
+ else :
57
+ self .tz = None
58
+
59
+ incident_templates_defaults = {
60
+ 'acknowledgement' : "{message}\n \n ###### {ack_time} by {author}\n \n ______\n " ,
61
+ 'investigation' : '' ,
62
+ 'resolving' : '' ,
63
+ }
64
+
65
+ self .templates = config .get ('templates' )
66
+ for template_name , default_value in incident_templates_defaults .items ():
67
+ if self .templates .get (template_name , None ) is None :
68
+ self .templates [template_name ] = default_value
69
+
70
+ self .initialized = True
24
71
25
72
26
73
@dataclass
@@ -39,7 +86,7 @@ def __str__(self):
39
86
return f"{ self .cachet_group_name } /{ self .cachet_component_name } - { self .zbx_serviceid } "
40
87
41
88
42
- def triggers_watcher (service_map : List [ZabbixCachetMap ]) -> bool :
89
+ def triggers_watcher (service_map : List [ZabbixCachetMap ], zapi : Zabbix , cachet : Cachet ) -> bool :
43
90
"""
44
91
Check zabbix triggers and update Cachet components
45
92
Zabbix Priority:
@@ -57,6 +104,7 @@ def triggers_watcher(service_map: List[ZabbixCachetMap]) -> bool:
57
104
4 - Fixed
58
105
@return: boolean
59
106
"""
107
+ config = Config ()
60
108
for i in service_map : # type: ZabbixCachetMap
61
109
# inc_status = 1
62
110
# comp_status = 1
@@ -79,9 +127,9 @@ def triggers_watcher(service_map: List[ZabbixCachetMap]) -> bool:
79
127
# component not operational mode. Resolve it.
80
128
last_inc = cachet .get_incident (i .cachet_component_id )
81
129
if str (last_inc ['id' ]) != '0' :
82
- if resolving_tmpl :
83
- inc_msg = resolving_tmpl .format (
84
- time = datetime .datetime .now (tz = tz ).strftime ('%b %d, %H:%M' ),
130
+ if config . templates [ 'resolving' ] :
131
+ inc_msg = config . templates [ 'resolving' ] .format (
132
+ time = datetime .datetime .now (tz = config . tz ).strftime ('%b %d, %H:%M' ),
85
133
) + cachet .get_incident (i .cachet_component_id )['message' ]
86
134
else :
87
135
inc_msg = cachet .get_incident (i .cachet_component_id )['message' ]
@@ -126,9 +174,9 @@ def triggers_watcher(service_map: List[ZabbixCachetMap]) -> bool:
126
174
# TODO: Add timezone?
127
175
# Move format to config file
128
176
author = msg .get ('name' , '' ) + ' ' + msg .get ('surname' , '' )
129
- ack_time = datetime .datetime .fromtimestamp (int (msg ['clock' ]), tz = tz ).strftime (
177
+ ack_time = datetime .datetime .fromtimestamp (int (msg ['clock' ]), tz = config . tz ).strftime (
130
178
'%b %d, %H:%M' )
131
- ack_msg = acknowledgement_tmpl .format (
179
+ ack_msg = config . templates [ 'acknowledgement' ] .format (
132
180
message = msg ['message' ],
133
181
ack_time = ack_time ,
134
182
author = author
@@ -146,14 +194,14 @@ def triggers_watcher(service_map: List[ZabbixCachetMap]) -> bool:
146
194
else :
147
195
comp_status = 2
148
196
149
- if not inc_msg and investigating_tmpl :
197
+ if not inc_msg and config . templates [ '' ] :
150
198
if zbx_event :
151
199
zbx_event_clock = int (zbx_event .get ('clock' ))
152
- zbx_event_time = datetime .datetime .fromtimestamp (zbx_event_clock , tz = tz ).strftime (
200
+ zbx_event_time = datetime .datetime .fromtimestamp (zbx_event_clock , tz = config . tz ).strftime (
153
201
'%b %d, %H:%M' )
154
202
else :
155
203
zbx_event_time = ''
156
- inc_msg = investigating_tmpl .format (
204
+ inc_msg = config . templates [ 'investigation' ] .format (
157
205
group = i .cachet_group_name ,
158
206
component = i .cachet_component_name ,
159
207
time = zbx_event_time ,
@@ -184,12 +232,14 @@ def triggers_watcher(service_map: List[ZabbixCachetMap]) -> bool:
184
232
return True
185
233
186
234
187
- def triggers_watcher_worker (service_map , interval , tr_event ):
235
+ def triggers_watcher_worker (service_map , interval , tr_event : threading . Event , zapi : Zabbix , cachet : Cachet ):
188
236
"""
189
237
Worker for triggers_watcher. Run it continuously with specific interval
190
238
@param service_map: list of tuples
191
239
@param interval: interval in seconds
192
240
@param tr_event: treading.Event object
241
+ @param zapi: Zabbix object
242
+ @param cachet: Cachet object
193
243
@return:
194
244
"""
195
245
logging .info ('start trigger watcher' )
@@ -198,7 +248,7 @@ def triggers_watcher_worker(service_map, interval, tr_event):
198
248
# Do not run if Zabbix is not available
199
249
if zapi .get_version ():
200
250
try :
201
- triggers_watcher (service_map )
251
+ triggers_watcher (service_map , zapi = zapi , cachet = cachet )
202
252
except Exception as e :
203
253
logging .error ('triggers_watcher() raised an Exception. Something gone wrong' )
204
254
logging .error (e , exc_info = True )
@@ -208,11 +258,13 @@ def triggers_watcher_worker(service_map, interval, tr_event):
208
258
logging .info ('end trigger watcher' )
209
259
210
260
211
- def init_cachet (services : List [ZabbixService ]) -> List [ZabbixCachetMap ]:
261
+ def init_cachet (services : List [ZabbixService ], zapi : Zabbix , cachet : Cachet ) -> List [ZabbixCachetMap ]:
212
262
"""
213
263
Init Cachet by syncing Zabbix service to it
214
264
Also func create mapping batten Cachet components and Zabbix IT services
215
- @param services: list of ZabbixService
265
+ :param services: list of ZabbixService
266
+ :param cachet: Cachet object
267
+ :param zapi: Zabbix object
216
268
@return: list of tuples
217
269
"""
218
270
# Zabbix Triggers to Cachet components id map
@@ -291,65 +343,39 @@ def read_config(config_f):
291
343
return None
292
344
293
345
294
- if __name__ == '__main__' :
295
-
296
- if os .getenv ('CONFIG_FILE' ) is not None :
297
- config_file = os .environ ['CONFIG_FILE' ]
298
- else :
299
- config_file = os .path .dirname (os .path .realpath (__file__ )) + '/config.yml'
300
- if not os .path .isfile (config_file ):
301
- logging .error (f"Config file { config_file } is absent. Set CONFIG_FILE to change path or create it there." )
302
- sys .exit (1 )
303
- config = read_config (config_file )
304
- if not config :
305
- sys .exit (1 )
306
- ZABBIX = config ['zabbix' ]
307
- CACHET = config ['cachet' ]
308
- SETTINGS = config ['settings' ]
309
-
310
- if SETTINGS .get ('time_zone' ):
311
- tz = pytz .timezone (SETTINGS ['time_zone' ])
312
- else :
313
- tz = None
314
-
315
- # Templates for incident displaying
316
- acknowledgement_tmpl_default = "{message}\n \n ###### {ack_time} by {author}\n \n ______\n "
317
- templates = config .get ('templates' )
318
- if templates :
319
- acknowledgement_tmpl = templates .get ('acknowledgement' , acknowledgement_tmpl_default )
320
- investigating_tmpl = templates .get ('investigating' , '' )
321
- resolving_tmpl = templates .get ('resolving' , '' )
322
- else :
323
- acknowledgement_tmpl = acknowledgement_tmpl_default
324
-
346
+ def main ():
325
347
exit_status = 0
348
+ config = Config ()
349
+
326
350
# Set Logging
327
- log_level = logging .getLevelName (SETTINGS ['log_level' ])
328
- log_level_requests = logging .getLevelName (SETTINGS ['log_level_requests' ])
351
+ log_level = logging .getLevelName (config . app_settings ['log_level' ])
352
+ log_level_requests = logging .getLevelName (config . app_settings ['log_level_requests' ])
329
353
logging .basicConfig (
330
354
level = log_level ,
331
355
format = '%(asctime)s %(levelname)s: (%(threadName)s) %(message)s' ,
332
356
datefmt = '%Y-%m-%d %H:%M:%S %Z'
333
357
)
334
358
logging .getLogger ("requests" ).setLevel (log_level_requests )
335
- logging .info (f'Zabbix Cachet v.{ __version__ } started (config: { config_file } )' )
359
+ logging .info (f'Zabbix Cachet v.{ __version__ } started (config: { config . config_file } )' )
336
360
inc_update_t = threading .Thread ()
337
361
event = threading .Event ()
338
362
try :
339
- zapi = Zabbix (ZABBIX ['server' ], ZABBIX ['user' ], ZABBIX ['pass' ], ZABBIX ['https-verify' ])
340
- cachet = Cachet (CACHET ['server' ], CACHET ['token' ], CACHET ['https-verify' ])
363
+ zapi = Zabbix (config .zabbix_config ['server' ], config .zabbix_config ['user' ], config .zabbix_config ['pass' ],
364
+ config .zabbix_config ['https-verify' ])
365
+ cachet = Cachet (config .cachet_config ['server' ], config .cachet_config ['token' ],
366
+ config .cachet_config ['https-verify' ])
341
367
logging .info ('Zabbix ver: {}. Cachet ver: {}' .format (zapi .version , cachet .version ))
342
368
zbxtr2cachet = ''
343
369
while True :
344
370
try :
345
371
logging .debug ('Getting list of Zabbix IT Services ...' )
346
- it_services = zapi .get_itservices (SETTINGS ['root_service' ])
372
+ it_services = zapi .get_itservices (config . app_settings ['root_service' ])
347
373
logging .debug ('Zabbix IT Services: {}' .format (it_services ))
348
374
# Create Cachet components and components groups
349
375
logging .debug ('Syncing Zabbix with Cachet...' )
350
- zbxtr2cachet_new = init_cachet (it_services )
376
+ zbxtr2cachet_new = init_cachet (it_services , zapi , cachet )
351
377
except ZabbixNotAvailable :
352
- time .sleep (SETTINGS ['update_comp_interval' ])
378
+ time .sleep (config . app_settings ['update_comp_interval' ])
353
379
continue
354
380
except ZabbixCachetException :
355
381
zbxtr2cachet_new = False
@@ -375,15 +401,22 @@ def read_config(config_f):
375
401
event .clear ()
376
402
inc_update_t = threading .Thread (name = 'Trigger Watcher' ,
377
403
target = triggers_watcher_worker ,
378
- args = (zbxtr2cachet , SETTINGS ['update_inc_interval' ], event ))
404
+ args = (zbxtr2cachet , config .app_settings ['update_inc_interval' ], event ,
405
+ zapi , cachet ))
379
406
inc_update_t .daemon = True
380
407
inc_update_t .start ()
381
- time .sleep (SETTINGS ['update_comp_interval' ])
382
-
408
+ time .sleep (config .app_settings ['update_comp_interval' ])
409
+ except requests .exceptions .ConnectionError as err :
410
+ logging .error (f"Failed to connect: { err } " )
411
+ exit_status = 1
383
412
except KeyboardInterrupt :
384
413
event .set ()
385
414
logging .info ('Shutdown requested. See you.' )
386
415
except Exception as error :
387
416
logging .exception (error )
388
417
exit_status = 1
389
418
sys .exit (exit_status )
419
+
420
+
421
+ if __name__ == '__main__' :
422
+ main ()
0 commit comments