Skip to content

Commit 40d5b25

Browse files
authored
Merge pull request #51 from ARGOeu/devel
Version 1.0.0
2 parents 8bff966 + 190fb6c commit 40d5b25

14 files changed

+490
-109
lines changed

CHANGELOG.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Changelog
2+
3+
All notable changes in argo-alert project are documented here
4+
5+
## 1.0.0 - (2023-02-02)
6+
7+
### Added:
8+
- ARGO-3901 Retrieve contacts from argo-web-api
9+
10+
### Changed:
11+
- ARGO-2856 Update simple feed parameters for json and csv
12+
- ARGO-2856 Update notifications to read contact info from json topology
13+
14+
15+
## 0.2.1 - (2020-12-16)
16+
17+
### Added:
18+
- ARGO-2079 Add original contacts feature when sending to testing emails
19+
20+
### Changed:
21+
- ARGO-2145 Finalize py3 version in argo-alert scripts
22+
23+
### Fixed:
24+
- ARGO-2027 Split gocdb contain email string into individual items
25+
26+
27+
## 0.2.0 - (2019-11-06)
28+
29+
### Added:
30+
- ARGO-2027 Split gocdb contain email string into individual items
31+
- ARGO-1710 publish group item status info
32+
- ARGO-1715 Consolidate alerts for endpoints that belong to multiple endpoint groups
33+
- ARGO-1640 Update alert publisher to forward new event information
34+
35+
### Fixed:
36+
- ARGO-1793 Fix ui_urls in alert mails to point correctly to the new web_ui
37+
38+
39+
## 0.1.2 - (2018-11-09)
40+
41+
### Fixed:
42+
- ARGO-1464 Update requests dep to 2.20
43+
44+
45+
## 0.1.1 - (2018-02-27)
46+
47+
### Added:
48+
- ARGO-933 Implement argo-alert publisher of argo-streaming events to alerta
49+
- ARGO-931 Alerta-mailer rule generator from gocdb contact data
50+
- ARGO-943 Use requests for verifying cert using capath (directory).
51+
- ARGO 960 Refactor rule generator for using notifications tag
52+
- ARGO-990 Accept a list of kafka endpoints for publisher
53+
- ARGO-999 Add boolean conf parameter for using contact notifications
54+
- ARGO-1026 Support different levels of entity groups when retrieving
55+
- ARGO-996 Add ability to generate rules using a group of test email
56+
57+
### Fixed:
58+
- ARGO-945 Fix endpoint_group field. Lowercase status field values

Jenkinsfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pipeline {
1010
stage ('Test Centos 7') {
1111
agent {
1212
docker {
13-
image 'argo.registry:5000/epel-7-ams'
13+
image 'argo.registry:5000/python3'
1414
args '-u jenkins:jenkins'
1515
}
1616
}
@@ -19,7 +19,7 @@ pipeline {
1919
cd ${WORKSPACE}/$PROJECT_DIR
2020
pipenv install -r requirements.txt
2121
pipenv run python setup.py install
22-
pipenv run coverage run --source=./argoalert -m py.test
22+
pipenv run coverage run --source=./argoalert -m pytest
2323
pipenv run coverage xml
2424
pipenv run py.test ./ --junitxml=./junit.xml
2525
'''

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ extra-emails=alert01@mail.example.foo,alert02@mail.example.foo
107107
aleart-timeout = 3600
108108
# group type of the tenant's top level group used in alert generation and mail template
109109
group-type = Group
110+
# endpoint type could be set to the default: endpoint or to another type (for example: service instance)
111+
endpoint-type = endpoint
112+
# ui-path-group is used in argo ui's result to define the correct type of group in results path
113+
ui-path-group = SITES
110114
# report name used in argo-web-ui url construction
111115
report = Critical
112116

argoalert/argoalert.py

Lines changed: 77 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def date_end_of_day(dt):
3737
return dt.replace(hour=23, minute=59, second=59)
3838

3939

40-
def ui_group_url(ui_endpoint, report, timestamp, grouptype, group, environment):
40+
def ui_group_url(ui_endpoint, report, timestamp, grouptype, group, environment, ui_path_group):
4141
"""Generate an http url to a relevant argo web ui endpoint group timeline page
4242
4343
Args:
@@ -54,10 +54,10 @@ def ui_group_url(ui_endpoint, report, timestamp, grouptype, group, environment)
5454
start_date = date_only_string(date_days_ago(parse_timestamp(timestamp), 3))
5555
end_date = date_only_string(parse_timestamp(timestamp))
5656
return "https://{0}/{1}/report-status/{2}/{3}/{4}?start={5}&end={6}".format(
57-
ui_endpoint, environment.lower(), report, grouptype.upper() + "S", group, start_date, end_date)
57+
ui_endpoint, environment.lower(), report, ui_path_group, group, start_date, end_date)
5858

5959

60-
def ui_endpoint_url(ui_endpoint, report, timestamp, grouptype, group, service, hostname, environment):
60+
def ui_endpoint_url(ui_endpoint, report, timestamp, grouptype, group, service, hostname, environment, ui_path_group):
6161
"""Generate an http url to a relevant argo web ui endpoint timeline page
6262
6363
Args:
@@ -76,10 +76,10 @@ def ui_endpoint_url(ui_endpoint, report, timestamp, grouptype, group, service, h
7676
start_date = date_only_string(date_days_ago(parse_timestamp(timestamp), 3))
7777
end_date = date_only_string(parse_timestamp(timestamp))
7878
return "http://{0}/{1}/report-status/{2}/{3}/{4}/{5}/{6}?start={7}&end={8}".format(
79-
ui_endpoint, environment.lower(), report, grouptype.upper() + "S", group, service, hostname, start_date, end_date)
79+
ui_endpoint, environment.lower(), report, ui_path_group, group, service, hostname, start_date, end_date)
8080

8181

82-
def transform(argo_event, environment, grouptype, timeout, ui_endpoint, report):
82+
def transform(argo_event, environment, grouptype, timeout, ui_endpoint, report, endpoint_type, ui_path_group):
8383
"""Transform an argo status event to an alerta alert
8484
8585
Args:
@@ -181,7 +181,7 @@ def transform(argo_event, environment, grouptype, timeout, ui_endpoint, report):
181181
environment.upper(), grouptype.capitalize(), group, status.upper())
182182
if ui_endpoint != "":
183183
attributes["_alert_url"] = ui_group_url(
184-
ui_endpoint, report, ts_monitored, grouptype, group, environment)
184+
ui_endpoint, report, ts_monitored, grouptype, group, environment, ui_path_group)
185185

186186
elif etype == "service":
187187
alerta_service.append("service")
@@ -192,11 +192,11 @@ def transform(argo_event, environment, grouptype, timeout, ui_endpoint, report):
192192
elif etype == "endpoint":
193193
alerta_service.append("endpoint")
194194
resource = service + "/" + hostname
195-
text = "[ {0} ] - Endpoint {1}/{2} is {3}".format(
196-
environment.upper(), hostname, service, status.upper())
195+
text = "[ {0} ] - {4} {1} ({2}) is {3}".format(
196+
environment.upper(), hostname, service, status.upper(), endpoint_type.capitalize())
197197
if ui_endpoint != "":
198198
attributes["_alert_url"] = ui_endpoint_url(
199-
ui_endpoint, report, ts_monitored, grouptype, group, service, hostname, environment)
199+
ui_endpoint, report, ts_monitored, grouptype, group, service, hostname, environment, ui_path_group)
200200

201201
elif etype == "metric":
202202
alerta_service.append("metric")
@@ -235,7 +235,7 @@ def read_and_send(message, environment, alerta_url, alerta_token, options):
235235

236236
try:
237237
alerta = transform(argo_event, environment,
238-
options["group_type"], options["timeout"], options["ui_endpoint"], options["report"])
238+
options["group_type"], options["timeout"], options["ui_endpoint"], options["report"], options["endpoint_type"], options["ui_path_group"])
239239
except KeyError as e:
240240
logging.warning("WRONG JSON SCHEMA: " + message.value)
241241
return
@@ -341,26 +341,53 @@ def gocdb_to_contacts(gocdb_xml, use_notif_flag, test_emails):
341341
name = name_tags[0].firstChild.nodeValue
342342
service = service_tags[0].firstChild.nodeValue
343343
c["name"] = "\\/" + service + "\\/" + name
344-
344+
345345
if test_emails is None:
346346
c["emails"] = item.firstChild.nodeValue
347347
else:
348348
c["emails"] = test_emails[indx % len(test_emails)]
349349
c["original_email"] = item.firstChild.nodeValue
350350
indx = indx + 1
351-
351+
352352
contacts.append(c)
353353

354354
return contacts
355355

356356

357+
def gen_endpoint_contacts_from_groups(group_data, endpoint_data, group_filter=None):
358+
"""Parse both endpoint and group topology data and refactor endpoint data using
359+
notification information from groups
360+
361+
Args:
362+
endpoint_data (obj): list of endpoint topology data
363+
group_data (string): list of group topology data
364+
group_filter (string): keep only groups of a specific type e.g. SITES
365+
366+
Returns:
367+
obj: list containing endpoint topology data with notification detailes borrowed from groups
368+
"""
369+
group_index = {}
370+
gen_endpoints = []
371+
for item in group_data:
372+
group_index[item["subgroup"]] = item["notifications"]
373+
374+
for item in endpoint_data:
375+
endpoint_group = item["group"]
376+
if group_filter and group_filter != item["type"]:
377+
# skip endpoint if we have enabled a group filter and its group type doesn't match
378+
continue
379+
if endpoint_group in group_index:
380+
item["notifications"] = group_index[endpoint_group]
381+
gen_endpoints.append(item)
382+
383+
return gen_endpoints
357384

358385
def argo_web_api_to_contacts(endpoint_data, group_data, use_notif=False, test_emails=None):
359386
"""Contact argo-web-api endpoint and retrieve contacts for topology endpoints and groups
360387
361388
Args:
362-
api_endpoint (string): endpoint for argo-web-api instance
363-
access_key (string): argo-web-api access key
389+
endpoint_data (obj): list of endpoint topology data
390+
grou_data (string): list of group topology data
364391
verify (bool, optional): Set https verification on/off. Defaults to True.
365392
test_emails (string, optional): Set test emails for notifications. Defaults to None
366393
@@ -371,40 +398,61 @@ def argo_web_api_to_contacts(endpoint_data, group_data, use_notif=False, test_em
371398
get_notif_always = not use_notif
372399

373400

374-
401+
375402
contacts = []
376403
subgroup_types = {}
377404
# iterate over endpoints but also get subgroup types
378405
for indx, endpoint in enumerate(endpoint_data):
379406
subgroup_types[endpoint["group"]]=endpoint["type"]
380407
if "notifications" in endpoint:
381-
if get_notif_always or (endpoint["notifications"]["enabled"] == True):
382-
name = "{}\\/{}".format(endpoint["service"].replace(".","\\."),endpoint["hostname"].replace(".","\\."))
408+
# if notifications is empty
409+
if get_notif_always or ("enabled" in endpoint["notifications"] and endpoint["notifications"]["enabled"] == True):
410+
name = "{}\\/{}".format(endpoint["service"].replace(".","\\."), endpoint["hostname"].replace(".","\\."))
411+
contact = ""
383412
if not test_emails:
384-
contact = ";".join(endpoint["notifications"]["contacts"])
413+
if "contacts" in endpoint["notifications"]:
414+
contact = ";".join(endpoint["notifications"]["contacts"])
385415
else:
386-
contact = test_emails[indx % len(test_emails)]
387-
contacts.append({"name":name, "emails":contact, "type":endpoint["type"]})
416+
contact = test_emails[indx % len(test_emails)]
417+
if contact != "":
418+
contacts.append({"name": name, "emails": contact, "type": "endpoint"})
419+
420+
# consolidate rules with duplicate names (same endpoint belonging to multiple groups)
421+
contact_index = {}
422+
for contact in contacts:
423+
if contact["name"] in contact_index:
424+
# append emails to existing contact rule
425+
contact_index[contact["name"]]["emails"] = contact_index[contact["name"]]["emails"] + ";" + contact["emails"]
426+
else:
427+
contact_index[contact["name"]] = contact
428+
429+
contacts = list(contact_index.values())
430+
388431
# iterate now over group data
389432
for indx, group in enumerate(group_data):
390433
if "notifications" not in group:
391434
continue
392-
if get_notif_always or (group["notifications"]["enabled"] == True):
435+
436+
if get_notif_always or ("enabled" in group["notifications"] and group["notifications"]["enabled"] == True):
393437
name = group["subgroup"]
394438
if name not in subgroup_types:
395439
continue
396440
subgroup_type = subgroup_types[name]
441+
contact = ""
397442
if not test_emails:
398-
contact = ";".join(endpoint["notifications"]["contacts"])
443+
if "contacts" in group["notifications"]:
444+
contact = ";".join(group["notifications"]["contacts"])
399445
else:
400446
contact = test_emails[indx % len(test_emails)]
401-
contacts.append({"name":name, "emails":contact, "type":subgroup_type})
447+
if contact != "":
448+
contacts.append({"name":name, "emails":contact, "type":subgroup_type})
449+
402450
return contacts
403451

404452

405453
def get_argo_web_api_data(api_endpoint, access_key, verify=True, topology_type="endpoints"):
406454
# get endpoint topology
407-
455+
408456
api_url = api_endpoint
409457
if topology_type == "endpoints":
410458
api_url = api_url + "/api/v2/topology/endpoints"
@@ -414,7 +462,7 @@ def get_argo_web_api_data(api_endpoint, access_key, verify=True, topology_type="
414462
logging.info("Requesting topology of {} from argo-web-api: {}".format(topology_type,api_url))
415463
r = requests.get(api_url, headers={
416464
'x-api-key': access_key, 'Accept': 'application/json'}, verify=verify)
417-
465+
418466
if r.status_code == 200:
419467
logging.info("Argo-web-api topology data retrieved successfully")
420468
return json.loads(r.text)["data"]
@@ -480,15 +528,15 @@ def contacts_to_alerta(contacts, extras=None, environment=None):
480528
rule_fields.append(
481529
{"field": "environment", "regex": "{0}".format(environment)})
482530
rule_contacts = re.split(";|,", c["emails"].replace(" ", ""))
483-
531+
484532
if extras:
485533
rule_contacts.extend(extras)
486534
rule_exclude = True
487535

488536
rule = {"name": rule_name, "fields": rule_fields,
489537
"contacts": rule_contacts, "exclude": rule_exclude}
490-
491-
538+
539+
492540

493541
# Check if contacts have original emails -- used during testing
494542
if "original_email" in c:
@@ -536,10 +584,6 @@ def get_gocdb(api_url, auth_info, ca_bundle):
536584

537585
return ""
538586

539-
540-
541-
542-
543587
def write_rules(rules, outfile):
544588
"""Writes alerta email rules to a specific output file
545589
@@ -551,4 +595,4 @@ def write_rules(rules, outfile):
551595
json_str = json.dumps(rules, indent=4)
552596
logging.info("Saving rule to file: " + outfile)
553597
with open(outfile, "w") as output_file:
554-
output_file.write(json_str)
598+
output_file.write(json_str)

bin/argo-alert-publisher

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/usr/bin/env python
1+
#!/opt/alerta/bin/python
22

33
from argparse import ArgumentParser
44
from configparser import ConfigParser
@@ -19,7 +19,7 @@ def main(args=None):
1919
environment = parser.get("alerta", "environment")
2020
alerta_token = parser.get("alerta", "token")
2121
log_level = parser.get("logging", "level")
22-
logging.basicConfig(level=log_level)
22+
logging.basicConfig(level=log_level,format='%(asctime)s - %(levelname)s - %(message)s')
2323

2424
# default alert timeout value
2525
alert_timeout = 3600
@@ -30,6 +30,15 @@ def main(args=None):
3030
if parser.has_option("alerta", "group-type"):
3131
group_type = parser.get("alerta", "group-type")
3232

33+
ui_path_group = "SITES"
34+
if parser.has_option("alerta", "ui-path-group"):
35+
ui_path_group = parser.get("alerta", "ui-path-group")
36+
37+
38+
endpoint_type = "Endpoint"
39+
if parser.has_option("alerta", "endpoint-type"):
40+
endpoint_type = parser.get("alerta", "endpoint-type")
41+
3342
report = "Critical"
3443
if parser.has_option("alerta", "report"):
3544
report = parser.get("alerta", "report")
@@ -39,8 +48,10 @@ def main(args=None):
3948
ui_endpoint = parser.get("alerta", "ui_endpoint")
4049

4150
# create alert options dictionary
42-
options = {"timeout": alert_timeout, "group_type": group_type,
43-
"report": report, "ui_endpoint": ui_endpoint}
51+
options = {"timeout": alert_timeout, "group_type": group_type, "endpoint_type": endpoint_type,
52+
"report": report, "ui_endpoint": ui_endpoint, "ui_path_group":ui_path_group }
53+
54+
4455

4556
argoalert.start_listening(environment, kafka_endpoint,
4657
kafka_topic, alerta_endpoint, alerta_token, options)
@@ -56,4 +67,4 @@ if __name__ == "__main__":
5667

5768
# Parse the command line arguments accordingly and introduce them to
5869
# main...
59-
sys.exit(main(arg_parser.parse_args()))
70+
sys.exit(main(arg_parser.parse_args()))

0 commit comments

Comments
 (0)