Skip to content

Commit 07278c4

Browse files
committed
In progress of translating ruby equivalent
1 parent 20bf988 commit 07278c4

File tree

2 files changed

+242
-4
lines changed

2 files changed

+242
-4
lines changed

sensu_plugin/handler.py

Lines changed: 194 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,206 @@
66
# Released under the same terms as Sensu (the MIT license); see LICENSE
77
# for details.
88

9+
from __future__ import print_function
10+
import os
11+
import sys
12+
import requests
13+
try:
14+
from urlparse import urlparse
15+
except:
16+
from urllib.parse import urlparse
17+
from utils import *
918

1019
class SensuHandler(object):
1120
def __init__(self):
12-
pass
21+
# Parse the stdin into a global event object
22+
stdin = sys.stdin.read()
23+
self.read_event(stdin)
24+
25+
# Prepare global settings
26+
self.settings = get_settings()
27+
self.api_settings = self.get_api_settings()
28+
29+
# Filter (deprecated) and handle
30+
self.filter()
31+
self.handle()
32+
33+
def read_event(self, check_result):
34+
'''
35+
Convert the piped check result (json) into a global 'event' dict
36+
'''
37+
try:
38+
self.event = json.loads(check_result)
39+
self.event['occurrences'] = self.event.get('occurrences', 1)
40+
self.event['check'] = self.event.get('check', {})
41+
self.event['client'] = self.event.get('client', {})
42+
except Exception as e:
43+
print('error reading event: ' + e.message)
44+
sys.exit(1)
1345

1446
def handle(self):
15-
pass
47+
'''
48+
Method that should be overwritten to provide handler logic.
49+
'''
50+
print('ignoring event -- no handler defined')
1651

1752
def filter(self):
18-
pass
53+
'''
54+
Filters exit the proccess if the event should not be handled.
55+
56+
Filtering events is deprecated and will be removed in a future release.
57+
'''
58+
59+
if self.deprecated_filtering_enabled():
60+
print('warning: event filtering in sensu-plugin is deprecated, see http://bit.ly/sensu-plugin')
61+
self.filter_disabled()
62+
self.filter_silenced()
63+
self.filter_dependencies()
64+
65+
if self.deprecated_occurrence_filtering_enabled():
66+
print('warning: occurrence filtering in sensu-plugin is deprecated, see http://bit.ly/sensu-plugin')
67+
self.filter_repeated()
68+
69+
def deprecated_filtering_enabled(self):
70+
'''
71+
Evaluates whether the event should be processed by any of the
72+
filter methods in this library. Defaults to true,
73+
i.e. deprecated filters are run by default.
74+
75+
returns bool
76+
'''
77+
return self.event['check'].get('enable_deprecated_filtering', False)
78+
79+
80+
def deprecated_occurrence_filtering_enabled(self):
81+
'''
82+
Evaluates whether the event should be processed by the
83+
filter_repeated method. Defaults to true, i.e. filter_repeated
84+
will filter events by default.
85+
86+
returns bool
87+
'''
88+
89+
return self.event['check'].get('enable_deprecated_occurrence_filtering', False)
1990

2091
def bail(self, msg):
21-
pass
92+
'''
93+
Gracefully terminate with message
94+
'''
95+
client_name = self.event['client'].get('name', 'error:no-client-name')
96+
check_name = self.event['client'].get('name', 'error:no-check-name')
97+
print('{}: {}/{}'.format(msg, client_name, check_name))
98+
sys.exit(0)
99+
100+
def get_api_settings(self):
101+
'''
102+
Return a hash of API settings derived first from ENV['SENSU_API_URL'] if set,
103+
then Sensu config `api` scope if configured, and finally falling back to
104+
to ipv4 localhost address on default API port.
105+
106+
return dict
107+
'''
108+
109+
SENSU_API_URL = os.environ.get('SENSU_API_URL')
110+
if SENSU_API_URL:
111+
uri = urlparse(SENSU_API_URL)
112+
self.api_settings = {
113+
'host': '{0}//{1}'.format(uri.scheme,uri.hostname),
114+
'port': uri.port,
115+
'user': uri.username,
116+
'password': uri.password
117+
}
118+
else:
119+
self.api_settings = self.settings.get('api',{})
120+
self.api_settings['host'] = self.api_settings.get('host', '127.0.0.1')
121+
self.api_settings['port'] = self.api_settings.get('port', 4567)
122+
123+
# API requests
124+
def api_request(method, path, blk):
125+
if not hasattr(self, 'api_settings'):
126+
ValueError('api.json settings not found')
127+
128+
if method.lower() == 'get':
129+
_request = requests.get
130+
elif method.lower() == 'post':
131+
_request = requests.post
132+
133+
domain = self.api_settings['host']
134+
# TODO: http/https
135+
uri = 'http://{}:{}{}'.format(domain, self.api_settings['port'], path)
136+
if self.api_settings['user'] and self.api_settings['password']:
137+
auth = (self.api_settings['user'], self.api_settings['password'])
138+
else:
139+
auth = ()
140+
req = _request(uri, auth=auth)
141+
return req
142+
143+
def stash_exists(self, path):
144+
return self.api_request('get', '/stash' + path).status_code == 200
145+
146+
def event_exists(self, client, check):
147+
return self.api_request('get', '/events/' + client + '/' + check).status_code == 200
148+
149+
# Filters
150+
def filter_disabled(self):
151+
if self.event['check']['alert'] == False:
152+
bail('alert disabled')
153+
154+
def filter_silenced(self):
155+
stashes = [
156+
('client', '/silence/' + self.event['client']['name']),
157+
('check', '/silence/' + self.event['client']['name'] + '/' + self.event['check']['name']),
158+
('check', '/silence/all/' + self.event['check']['name'])
159+
]
160+
for scope, path in stashes:
161+
if stash_exists(path):
162+
bail(scope + ' alerts silenced')
163+
# TODO: Timeout for querying Sensu API?
164+
# More appropriate in the api_request method?
165+
166+
def filter_dependencies(self):
167+
dependencies = self.event['check'].get('dependencies', None)
168+
if dependencies == None or not isinstance(dependencies, list):
169+
return
170+
for dependency in self.event['check']['dependencies']:
171+
if len(str(dependency)) == 0:
172+
continue
173+
dependency_split = tuple(dependency.split('/'))
174+
# If there's a dependency on a check from another client, then use
175+
# that client name, otherwise assume same client.
176+
if len(dependency_split) == 2:
177+
client,check = dependency_split
178+
else:
179+
client = self.event['client']['name']
180+
check = dependency_split[0]
181+
if self.event_exists(client, check):
182+
bail('check dependency event exists')
183+
184+
185+
def filter_repeated(self):
186+
defaults = {
187+
'occurrences': 1,
188+
'interval': 30,
189+
'refresh': 1800
190+
}
191+
192+
# Override defaults with anything defined in the settings
193+
if isinstance(self.settings['sensu_plugin'], dict):
194+
defaults.update(settings['sensu_plugin'])
195+
end
196+
197+
occurrences = int(self.event['check'].get('occurrences', defaults['occurrences']))
198+
interval = int(self.event['check'].get('interval', defaults['interval']))
199+
refresh = int(self.event['check'].get('refresh', defaults['refresh']))
200+
201+
if self.event['occurrences'] < occurrences:
202+
bail('not enough occurrences')
203+
204+
if self.event['occurrences'] > occurrences and self.event['action'] == 'create':
205+
return
206+
207+
number = int(refresh / interval)
208+
if (number == 0) or ((self.event['occurrences'] - occurrences) % number == 0):
209+
return
210+
211+
bail('only handling every ' + str(number) + ' occurrences')

sensu_plugin/utils/__init__.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import os
2+
import json
3+
4+
def config_files():
5+
SENSU_LOADED_TEMPFILE = os.environ.get('SENSU_LOADED_TEMPFILE')
6+
SENSU_CONFIG_FILES = os.environ.get('SENSU_CONFIG_FILES')
7+
if SENSU_LOADED_TEMPFILE and os.path.isfile(SENSU_LOADED_TEMPLATE):
8+
with open(SENSU_LOADED_TEMPLATE, 'r') as template:
9+
contents = template.read()
10+
return contents.split(':')
11+
elif SENSU_CONFIG_FILES:
12+
return SENSU_CONFIG_FILES.split(':')
13+
else:
14+
files = ['/etc/sensu/config.json']
15+
[files.append('/etc/sensu/conf.d/' + filename) for filename in os.listdir('/etc/sensu/conf.d') if os.path.splitext(filename)[1] == '.json']
16+
return files
17+
18+
def get_settings():
19+
settings = {}
20+
for config_file in config_files():
21+
config_contents = load_config(config_file)
22+
if config_contents != None:
23+
settings = deep_merge(settings, config_contents)
24+
return settings
25+
26+
def load_config(filename):
27+
try:
28+
with open(filename, 'r') as config_file:
29+
return json.loads(config_file.read())
30+
except:
31+
{}
32+
33+
def deep_merge(dict_one, dict_two):
34+
merged = dict_one.copy()
35+
for key,value in dict_two.items():
36+
# value is equivalent to dict_two[key]
37+
if (key in dict_one and
38+
isinstance(dict_one[key], dict) and
39+
isinstance(value, dict)):
40+
merged[key] = deep_merge(dict_one[key], value)
41+
elif (key in dict_one and
42+
isinstance(dict_one[key], list) and
43+
isinstance(value, list)):
44+
merged[key] = list(set(dict_one[key] + value))
45+
else:
46+
merged[key] = value
47+
return merged
48+

0 commit comments

Comments
 (0)