Completed
Pull Request — master (#502)
by
unknown
02:52
created

_get_auth_token()   B

Complexity

Conditions 6

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

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