-
Notifications
You must be signed in to change notification settings - Fork 14
/
alert.py
executable file
·322 lines (277 loc) · 15.9 KB
/
alert.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
#!/usr/bin/env python
"""A front-end for the alertlib library.
This is just a simple frontend to allow using alertlib from the
commandline. You should prefer to use the library directly for uses
within Python.
"""
from __future__ import print_function
import argparse
import json
import logging
import sys
import alertlib
import alertlib.graphite
import alertlib.stackdriver
import alertlib.alerta
DEFAULT_SEVERITY = logging.INFO
class _MakeList(argparse.Action):
"""Parse the argument as a comma-separated list."""
def __call__(self, parser, namespace, value, option_string=None):
if not value:
return []
setattr(namespace, self.dest, [x.strip() for x in value.split(',')])
class _ParseSeverity(argparse.Action):
"""Parse the argument as a logging.<severity>."""
def __call__(self, parser, namespace, value, option_string=None):
setattr(namespace, self.dest, getattr(logging, value.upper()))
def setup_parser():
"""Create an ArgumentParser for alerting."""
parser = argparse.ArgumentParser(
description=('Send a message to one or more alerting services. '
'The message is read from stdin.'))
parser.add_argument('--hipchat', default=[], action=_MakeList,
help=('Send to HipChat. Argument is a comma-'
'separated list of room names. '
'May specify --severity and/or --summary '
'and/or --chat-sender. '
'May specify --color and/or --notify '
'(if omitted we determine automatically).'))
parser.add_argument('--slack', default=[], action=_MakeList,
help=('Send to Slack. Argument is a comma-'
'separated list of channel names. '
'May specify --severity and/or --summary. '
'May specify --chat-sender and/or '
'--icon-emoji or --icon-url '
'(if omitted Slack determines automatically).'
'May specify --slack-simple-mesage or '
'--slack-attachment.'))
parser.add_argument('--mail', default=[], action=_MakeList,
help=('Send to KA email. Argument is a comma-'
'separated list of usernames. '
'May specify --summary as a subject line; '
'if missing we figure it out automatically. '
'May specify --cc and/or --bcc.'))
parser.add_argument('--asana', default=[], action=_MakeList,
help=('DEPRECATED: Use --bugtracker.'))
parser.add_argument('--pagerduty', default=[], action=_MakeList,
help=('Send to PagerDuty. Argument is a comma-'
'separated list of PagerDuty services. '
'May specify --summary as a brief summary; '
'if missing we figure it out automatically. '))
parser.add_argument('--logs', action='store_true',
help=('Send to syslog. May specify --severity.'))
parser.add_argument('--graphite', default=[], action=_MakeList,
help=('Send to graphite. Argument is a comma-'
'separated list of statistics to update. '
'May specify --graphite_value and '
'--graphite_host.'))
parser.add_argument('--stackdriver', default=[], action=_MakeList,
help=('Send to Stackdriver. Argument is a comma-'
'separated list of metrics to update, with '
'metric-label-values separated by pipes like so:'
' `logs.500|module=i18n`. '
'May specify --stackdriver_value'))
parser.add_argument('--aggregator', default=[], action=_MakeList,
help=('Send to aggregator such as Alerta.io. Argument'
'is comma-separated list of initiatives.'
'Must specify --aggregator-resource and '
'--aggregator-event-name and may specify '
'--severity and/or --summary'))
parser.add_argument('--bugtracker', default=[], action=_MakeList,
help=('Make issue in the bugtracker (right now that '
'means Jira, though Asana is also an option). '
'Argument is comma-separated list of initiatives'
' (e.g. "Infrastructure", "Test Prep") '
'May specify --cc with emails of those '
'who should be added as followers/watchers or '
'add --bug-tags for adding tags/labels to issue'
'May add --severity and/or --summary'))
parser.add_argument('--github-sha', default=None,
help=('The commit SHA to update the status of in '
'Github.'))
parser.add_argument('--github-owner', default='Khan',
help=('The owner of the repo to update.'))
parser.add_argument('--github-repo', default='webapp',
help=('The repo to update.'))
parser.add_argument('--github-status', default=None,
choices=['error', 'failure', 'pending', 'success'],
help=('Provide a precise status for a Github commit. '
'If not provided, will attempt to use Severity.'))
parser.add_argument('--github-target-url', default=None,
help=('An associated target URL to attach to the '
'commit status.'))
parser.add_argument('--github-context', default=None,
help=('The name of the job reporting the status.'))
parser.add_argument('--summary', default=None,
help=('Summary used as subject lines for emails, etc. '
'If omitted, we figure it out automatically '
'from the alert message. To suppress entirely, '
'pass --summary=""'))
parser.add_argument('--severity', default=DEFAULT_SEVERITY,
choices=['debug', 'info', 'warning', 'error',
'critical'],
action=_ParseSeverity,
help=('Severity of the message, which may affect '
'how we alert (default: %(default)s)'))
parser.add_argument('--html', action='store_true', default=False,
help=('Indicate the input should be treated as html'))
parser.add_argument('--chat-sender', default=None,
help=('Who we say sent this chat message. '
'In HipChat and with Slack bot tokens, defaults '
'to AlertiGator. With slack webhooks, defaults '
"to whatever the webhook's default is, likely "
'also AlertiGator.'))
parser.add_argument('--icon-emoji', default=None,
help=('The emoji sender to use for this message. '
'Slack only. Defaults to whatever the '
"webhook's default is, or :crocodile: if "
'using a bot token.'))
parser.add_argument('--icon-url', default=None,
help=('The icon URL to use for this message. '
'Slack only. Overridden by --icon-emoji.'))
parser.add_argument('--as-app', action='store_true', default=False,
help=('Send to Slack as a Slack application. If '
'using this option, must provide APP_BOT_TOKEN '
'in secrets.'))
parser.add_argument('--color', default=None,
choices=['yellow', 'red', 'green', 'purple',
'gray', 'random'],
help=('Background color when sending to hipchat '
'(default depends on severity)'))
parser.add_argument('--notify', action='store_true', default=None,
help=('Cause a beep when sending to hipchat'))
parser.add_argument('--slack-intro', default='',
help=('If specified, text to put before the main '
'text. You can use @-alerts in the intro.'))
parser.add_argument('--slack-simple-message', action='store_true',
default=False,
help=('Pass message to slack using normal Markdown, '
'rather than rendering it "attachment" style.'))
parser.add_argument('--slack-attachments', default='[]',
help=('A list of slack attachment dicts, encoded as '
'json. Replaces `message` for sending to slack. '
'(See https://api.slack.com/docs/attachments.)'))
parser.add_argument('--slack-thread', default=None,
help=('A slack message timestamp to thread this with. '
'Must be the timestamp of a toplevel message in '
'the specified slack channel.'))
parser.add_argument('--cc', default=[], action=_MakeList,
help=('A comma-separated list of email addresses; '
'used with --mail'))
parser.add_argument('--sender-suffix', default=None,
help=('This adds "foo" to the sender address, which '
'is alertlib <[email protected]>.'))
parser.add_argument('--bcc', default=[], action=_MakeList,
help=('A comma-separated list of email addresses; '
'used with --mail'))
parser.add_argument('--bug-tags', default=[], action=_MakeList,
help=('A list of tags to add to this new task/issue'))
parser.add_argument('--graphite_value', default=1, type=float,
help=('Value to send to graphite for each of the '
'graphite statistics specified '
'(default %(default)s)'))
parser.add_argument('--graphite_host',
default=alertlib.graphite.DEFAULT_GRAPHITE_HOST,
help=('host:port to send graphite data to '
'(default %(default)s)'))
parser.add_argument('--stackdriver_value',
default=alertlib.stackdriver.DEFAULT_STACKDRIVER_VALUE,
type=float,
help=('Value to send to stackdriver for each of the '
'stackdriver statistics specified '
'(default %(default)s)'))
parser.add_argument('--stackdriver_project',
default=(
alertlib.stackdriver.DEFAULT_STACKDRIVER_PROJECT),
help=('Stackdriver project to send datapoints to '
'(default %(default)s)'))
parser.add_argument('-n', '--dry-run', action='store_true',
help=("Just log what we would do, but don't do it"))
parser.add_argument('--aggregator-resource', default=None, choices=sorted(
alertlib.alerta.MAP_RESOURCE_TO_ENV_SERVICE_AND_GROUP),
help=('Name of resource where alert originated to '
'send to aggregator. Only relevent when used '
'in conjunction with --aggregator. Choices '
'include: jenkins, mobile, test, toby, webapp'))
parser.add_argument('--aggregator-event-name', default=None,
help=('Name of the event for use by aggregator. '
'(e.g. ServiceDown, Error) Only relevent when '
'used in conjunction with --aggregator.'))
parser.add_argument('--aggregator-timeout', default=None, type=int,
help='Timeout in seconds before alert is '
'automatically resolved. If not specified then the '
'default aggregator timeout is used.')
parser.add_argument('--aggregator-resolve', dest='aggregator_resolve',
action='store_true',
help='Resolve this alert in the aggregator. '
'This will clear the alert from the Alerta dashboard.')
parser.set_defaults(aggregator_resolve=False)
return parser
def alert(message, args):
a = alertlib.Alert(message, args.summary, args.severity, html=args.html)
for room in args.hipchat:
a.send_to_hipchat(room, args.color, args.notify,
args.chat_sender or 'AlertiGator')
for channel in args.slack:
a.send_to_slack(channel, sender=args.chat_sender,
intro=args.slack_intro,
icon_url=args.icon_url, icon_emoji=args.icon_emoji,
simple_message=args.slack_simple_message,
attachments=json.loads(args.slack_attachments),
thread=args.slack_thread,
as_app=args.as_app)
if args.mail:
a.send_to_email(args.mail, args.cc, args.bcc, args.sender_suffix)
if args.github_sha:
a.send_to_github_commit_status(
args.github_sha,
state=args.github_status,
target_url=args.github_target_url,
description=args.summary,
context=args.github_context,
owner=args.github_owner,
repo=args.github_repo,
)
# TODO(jacqueline): The --asana flag is deprecated and all callers
# should be shifted to --bugtracker. Remove support for this tag when
# confirmed that there are no remaining callers using this flag.
for project in args.asana:
a.send_to_asana(project, tags=args.bug_tags, followers=args.cc)
for project in args.bugtracker:
a.send_to_bugtracker(project, labels=args.bug_tags, watchers=args.cc)
if args.pagerduty:
a.send_to_pagerduty(args.pagerduty)
if args.logs:
a.send_to_logs()
for statistic in args.graphite:
a.send_to_graphite(statistic, args.graphite_value,
args.graphite_host)
for statistic in args.stackdriver:
statistic_parts = statistic.split('|')
metric_name = statistic_parts[0]
metric_labels = dict(part.split('=') for part in statistic_parts[1:])
a.send_to_stackdriver(metric_name, args.stackdriver_value,
metric_labels=metric_labels,
project=args.stackdriver_project,
ignore_errors=False)
# subject to change if we decide go the route of having an alert
# be exclusive to just one initiative
for initiative in args.aggregator:
a.send_to_alerta(initiative,
resource=args.aggregator_resource,
event=args.aggregator_event_name,
timeout=args.aggregator_timeout,
resolve=args.aggregator_resolve)
def main(argv):
parser = setup_parser()
args = parser.parse_args(argv)
if sys.stdin.isatty():
print('>> Enter the message to alert, then hit control-D',
file=sys.stderr)
message = sys.stdin.read().strip()
if args.dry_run:
alertlib.enter_test_mode()
logging.getLogger().setLevel(logging.INFO)
alert(message, args)
if __name__ == '__main__':
main(sys.argv[1:])