Completed
Pull Request — master (#396)
by
unknown
01:46
created

_set_config_opts()   F

Complexity

Conditions 10

Size

Total Lines 49

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 10
dl 0
loc 49
rs 3.7894

How to fix   Complexity   

Complexity

Complex classes like _set_config_opts() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
#!/usr/bin/env python
2
3
import argparse
4
import base64
5
import httplib
6
try:
7
    import simplejson as json
8
except ImportError:
9
    import json
10
import os
11
import sys
12
import traceback
13
from urlparse import urljoin
14
15
try:
16
    import requests
17
    requests.packages.urllib3.disable_warnings()
18
except ImportError:
19
    raise ImportError('Missing dependency "requests". \
20
        Do ``pip install requests``.')
21
22
try:
23
    import yaml
24
except ImportError:
25
    raise ImportError('Missing dependency "pyyaml". \
26
        Do ``pip install pyyaml``.')
27
28
# ST2 configuration
29
30
ST2_API_BASE_URL = 'https://localhost/v1/'
31
ST2_USERNAME = None
32
ST2_PASSWORD = None
33
ST2_API_KEY = None
34
ST2_AUTH_TOKEN = None
35
36
ST2_AUTH_PATH = 'auth/tokens'
37
ST2_WEBHOOKS_PATH = 'api/webhooks/st2/'
38
ST2_TRIGGERS_PATH = 'api/triggertypes/'
39
ST2_TRIGGERTYPE_PACK = 'sensu'
40
ST2_TRIGGERTYPE_NAME = 'event_handler'
41
ST2_TRIGGERTYPE_REF = '.'.join([ST2_TRIGGERTYPE_PACK, ST2_TRIGGERTYPE_NAME])
42
43
# Sensu configuration
44
45
SENSU_HOST = 'localhost'
46
SENSU_PORT = 4567
47
SENSU_USER = ''
48
SENSU_PASS = ''
49
50
REGISTERED_WITH_ST2 = False
51
UNAUTHED = False
52
IS_API_KEY_AUTH = False
53
54
OK_CODES = [httplib.OK, httplib.CREATED, httplib.ACCEPTED, httplib.CONFLICT]
55
UNREACHABLE_CODES = [httplib.NOT_FOUND]
56
57
TOKEN_AUTH_HEADER = 'X-Auth-Token'
58
API_KEY_AUTH_HEADER = 'St2-Api-Key'
59
60
61
def _get_sensu_request_headers():
62
    b64auth = base64.b64encode(
63
        "%s:%s" %
64
        (SENSU_USER, SENSU_PASS))
65
    auth_header = "BASIC %s" % b64auth
66
    content_header = "application/json"
67
    return {"Authorization": auth_header, "Content-Type": content_header}
68
69
70
def _check_stash(client, check, verbose=False):
71
    sensu_api = "http://%s:%i" % (SENSU_HOST, SENSU_PORT)
72
    endpoints = [
73
        "silence/%s" % client,
74
        "silence/%s/%s" % (client, check),
75
        "silence/all/%s" % check]
76
77
    for endpoint in endpoints:
78
        url = "%s/stashes/%s" % (sensu_api, endpoint)
79
80
        if verbose:
81
            print('Getting sensu stash info from URL: %s' % url)
82
83
        try:
84
            response = requests.get(url, headers=_get_sensu_request_headers())
85
        except requests.exceptions.ConnectionError:
86
            traceback.print_exc(limit=20)
87
            msg = 'Couldn\'t connect to sensu to get stash info. Is sensu running on %s:%s?' % (
88
                SENSU_HOST, SENSU_PORT
89
            )
90
            raise Exception(msg)
91
92
        if verbose:
93
            print('Sensu response code: %s.' % response.status_code)
94
95
        if response.status_code == 200:
96
            print "Check or client is stashed"
97
            sys.exit(0)
98
99
100
def _get_st2_request_headers():
101
    headers = {}
102
103
    if not UNAUTHED:
104
        if IS_API_KEY_AUTH:
105
            headers[API_KEY_AUTH_HEADER] = ST2_API_KEY
106
        else:
107
            if ST2_AUTH_TOKEN:
108
                headers[TOKEN_AUTH_HEADER] = ST2_AUTH_TOKEN
109
            else:
110
                pass
111
112
    return headers
113
114
115
def _create_trigger_type(verbose=False):
116
    try:
117
        url = _get_st2_triggers_url()
118
        payload = {
119
            'name': ST2_TRIGGERTYPE_NAME,
120
            'pack': ST2_TRIGGERTYPE_PACK,
121
            'description': 'Trigger type for sensu event handler.'
122
        }
123
124
        headers = _get_st2_request_headers()
125
        headers['Content-Type'] = 'application/json; charset=utf-8'
126
127
        if verbose:
128
            print('POST to URL %s for registering trigger. Body = %s, headers = %s.' %
129
                  (url, payload, headers))
130
        post_resp = requests.post(url, data=json.dumps(payload),
131
                                  headers=headers, verify=False)
132
    except:
133
        traceback.print_exc(limit=20)
134
        raise Exception('Unable to connect to st2 endpoint %s.' % url)
135
    else:
136
        status = post_resp.status_code
137
        if status in UNREACHABLE_CODES:
138
            msg = 'Got response %s. Invalid triggers endpoint %s. Check configuration!' % (
139
                status,
140
                url
141
            )
142
            raise Exception(msg)
143
144
        if status not in OK_CODES:
145
            msg = 'Failed to register trigger type %s.%s with st2. HTTP_CODE: %s' % (
146
                ST2_TRIGGERTYPE_PACK, ST2_TRIGGERTYPE_NAME, status
147
            )
148
            raise Exception(msg)
149
        else:
150
            print('Registered trigger type with st2.')
151
152
153
def _get_auth_url():
154
    return urljoin(ST2_API_BASE_URL, ST2_AUTH_PATH)
155
156
157
def _get_auth_token(verbose=False):
158
    auth_url = _get_auth_url()
159
160
    if verbose:
161
        print('Will POST to URL %s to get auth token.' % auth_url)
162
163
    try:
164
        resp = requests.post(auth_url, json.dumps({'ttl': 5 * 60}),
165
                             auth=(ST2_USERNAME, ST2_PASSWORD), verify=False)
166
    except:
167
        traceback.print_exc(limit=20)
168
        raise Exception('Unable to connect to st2 endpoint %s.' % auth_url)
169
    else:
170
        if resp.status_code in UNREACHABLE_CODES:
171
            msg = 'Got response %s. Invalid auth endpoint %s. Check configuration!' % (
172
                resp.status_code,
173
                auth_url
174
            )
175
            raise Exception(msg)
176
        if resp.status_code not in OK_CODES:
177
            msg = 'Cannot get a valid auth token from %s. HTTP_CODE: %s' % (
178
                auth_url,
179
                resp.status_code
180
            )
181
            raise Exception(msg)
182
        return resp.json()['token']
183
184
185
def _register_trigger_with_st2(verbose=False):
186
    triggers_url = urljoin(_get_st2_triggers_url(), ST2_TRIGGERTYPE_REF)
187
188
    try:
189
        headers = _get_st2_request_headers()
190
        if verbose:
191
            print('Will GET from URL %s for detecting trigger %s.' % (
192
                  triggers_url, ST2_TRIGGERTYPE_REF))
193
            print('Request headers: %s' % headers)
194
        get_resp = requests.get(triggers_url, headers=headers, verify=False)
195
196
        if get_resp.status_code != httplib.OK:
197
            _create_trigger_type(verbose=verbose)
198
        else:
199
            body = json.loads(get_resp.text)
200
            if len(body) == 0:
201
                _create_trigger_type(verbose=verbose)
202
    except:
203
        traceback.print_exc(limit=20)
204
        raise Exception('Unable to connect to st2 endpoint %s.' % triggers_url)
205
    else:
206
        if verbose:
207
            print('Successfully registered trigger %s with st2.' % ST2_TRIGGERTYPE_REF)
208
209
210
def _get_st2_triggers_url():
211
    url = urljoin(ST2_API_BASE_URL, ST2_TRIGGERS_PATH)
212
    return url
213
214
215
def _get_st2_webhooks_url():
216
    url = urljoin(ST2_API_BASE_URL, ST2_WEBHOOKS_PATH)
217
    return url
218
219
220
def _post_webhook(url, body, verbose=False):
221
    headers = _get_st2_request_headers()
222
    headers['X-ST2-Integration'] = 'sensu.'
223
    headers['St2-Trace-Tag'] = body['payload']['id']
224
    headers['Content-Type'] = 'application/json; charset=utf-8'
225
226
    try:
227
        if verbose:
228
            print('Webhook POST: url: %s, headers: %s, body: %s\n' % (url, headers, body))
229
        r = requests.post(url, data=json.dumps(body), headers=headers, verify=False)
230
    except:
231
        raise Exception('Cannot connect to st2 endpoint %s.' % url)
232
    else:
233
        status = r.status_code
234
235
        if status in UNREACHABLE_CODES:
236
            msg = 'Webhook URL %s does not exist. Check StackStorm installation!'
237
            raise Exception(msg)
238
239
        if status not in OK_CODES:
240
            sys.stderr.write('Failed posting sensu event to st2. HTTP_CODE: \
241
                %d\n' % status)
242
        else:
243
            sys.stdout.write('Sent sensu event to st2. HTTP_CODE: \
244
                %d\n' % status)
245
246
247
def _post_event_to_st2(payload, verbose=False):
248
    body = {}
249
    body['trigger'] = ST2_TRIGGERTYPE_REF
250
251
    try:
252
        body['payload'] = json.loads(payload)
253
    except:
254
        print('Invalid JSON payload %s.' % payload)
255
        sys.exit(3)
256
257
    try:
258
        client = body['payload']['client']['name']
259
        check = body['payload']['check']['name']
260
    except KeyError:
261
        print('Invalid payload spec %s.' % payload)
262
263
    if not _check_stash(client, check, verbose=verbose):
264
        try:
265
            _post_webhook(url=_get_st2_webhooks_url(), body=body, verbose=verbose)
266
        except:
267
            traceback.print_exc(limit=20)
268
            print('Cannot send event to st2.')
269
            sys.exit(4)
270
271
272
def _register_with_st2(verbose=False):
273
    global REGISTERED_WITH_ST2
274
    try:
275
        if not REGISTERED_WITH_ST2:
276
            if verbose:
277
                print('Checking if trigger %s registered with st2.' % ST2_TRIGGERTYPE_REF)
278
            _register_trigger_with_st2(verbose=verbose)
279
            REGISTERED_WITH_ST2 = True
280
    except:
281
        traceback.print_exc(limit=20)
282
        sys.stderr.write(
283
            'Failed registering with st2. Won\'t post event.\n')
284
        sys.exit(2)
285
286
287
def _set_config_opts(config_file, verbose=False, unauthed=False):
288
    global ST2_USERNAME
289
    global ST2_PASSWORD
290
    global ST2_API_KEY
291
    global ST2_AUTH_TOKEN
292
    global ST2_API_BASE_URL
293
    global SENSU_HOST
294
    global SENSU_PORT
295
    global SENSU_USER
296
    global SENSU_PASS
297
    global UNAUTHED
298
    global IS_API_KEY_AUTH
299
300
    UNAUTHED = unauthed
301
302
    if not os.path.exists(config_file):
303
        print('Configuration file %s not found. Exiting!!!' % config_file)
304
        sys.exit(1)
305
306
    with open(config_file) as f:
307
        config = yaml.safe_load(f)
308
309
        if verbose:
310
            print('Contents of config file: %s' % config)
311
312
        ST2_USERNAME = config['st2_username']
313
        ST2_PASSWORD = config['st2_password']
314
        ST2_API_KEY = config['st2_api_key']
315
        ST2_API_BASE_URL = config['st2_api_base_url']
316
        SENSU_HOST = config.get('sensu_host', 'localhost')
317
        SENSU_PORT = config.get('sensu_port', '4567')
318
        SENSU_USER = config.get('sensu_user', None)
319
        SENSU_PASS = config.get('sensu_pass', None)
320
321
    if ST2_API_KEY:
322
        IS_API_KEY_AUTH = True
323
324
    if verbose:
325
        print('Unauthed? : %s' % UNAUTHED)
326
        print('API key auth?: %s' % IS_API_KEY_AUTH)
327
328
    if not UNAUTHED and not IS_API_KEY_AUTH:
329
        try:
330
            if not ST2_AUTH_TOKEN:
331
                if verbose:
332
                    print('No auth token found. Let\'s get one from StackStorm!')
333
                ST2_AUTH_TOKEN = _get_auth_token(verbose=verbose)
334
        except:
335
            raise Exception('Unable to negotiate an auth token. Exiting!')
336
337
338
def main(config_file, payload, verbose=False, unauthed=False):
339
    _set_config_opts(config_file=config_file, unauthed=unauthed, verbose=verbose)
340
    _register_with_st2(verbose=verbose)
341
    _post_event_to_st2(payload, verbose=verbose)
342
343
344
if __name__ == '__main__':
345
    parser = argparse.ArgumentParser(description='StackStorm sensu event handler.')
346
    parser.add_argument('config_path',
347
                        help='Exchange to listen on')
348
    parser.add_argument('--verbose', '-v', required=False, action='store_true',
349
                        help='Verbose mode.')
350
    parser.add_argument('--unauthed', '-u', required=False, action='store_true',
351
                        help='Allow to post to unauthed st2. E.g. when auth is disabled ' +
352
                        'server side.')
353
    args = parser.parse_args()
354
    payload = sys.stdin.read().strip()
355
    main(config_file=args.config_path, payload=payload, verbose=args.verbose,
356
         unauthed=args.unauthed)
357