-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrun_checks_on_console.py
executable file
·156 lines (113 loc) · 4.66 KB
/
run_checks_on_console.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
#!/usr/bin/env python3
""" This python module will run the checks from src/config.js and produce output on the
console. If all checks complete successfully, the return code will be 0 (success) --
otherwise, it will be 1 (error). This makes it suitable for use from a cron job.
Requirements:
pip3 install regex requests pyyaml termcolor
Example usage from cron (checks every five minutes, emails full report on failure):
SHELL=/bin/bash
*/5 * * * * output=$(/path/to/run_checks_on_console.py -g) || echo "$output"
"""
import argparse
import sys
import textwrap
import regex
import requests
import yaml
from termcolor import colored as _colored
LINE_LENGTH = 80
def colored(text, *args, **kwargs):
if sys.stdout.isatty():
return _colored(text, *args, **kwargs)
return text
def get_config_from_github():
"""Fetch the latest config from GitHub -- useful so that the script/repo doesn't have
to be re-deployed if config checks are added/changed/deleted."""
config_url = "https://raw.githubusercontent.com/ADHO/adho-status/main/src/config.js"
response = requests.get(config_url)
return response.text
def parse_config(config):
"""Get the JavaScript object from the config file, and parse it to a python
dictionary. (Note: it's **not** JSON, hence the need for pyyaml.)"""
services_object_regex = regex.compile(r"services\s*=\s*(\{(?:[^{}]|(?1))*\})")
checks = services_object_regex.search(config)
return yaml.safe_load(checks.group(1))
def test_response_for_text(response, service_config):
if service_config["matchText"] not in response.text:
print(response.text, file=sys.stderr)
return service_config["matchText"] in response.text
def test_response_code_200(response, service_config):
return response.status_code == 200
def test_response_against_regex(response, service_config):
def parse_regex(js_regex):
"""This is adequate for now, but this function may need to be upgraded to
deal with flags and/or with differences between Regex syntax in Python
and Javascript (see https://pypi.org/project/js-regex/)."""
return js_regex[1:-1]
reg = regex.compile(parse_regex(service_config["regex"]))
return reg.search(response.text)
def do_check(check):
notice = f"Checking {check['displayName']}..."
print(notice, end=" ", flush=True)
try:
response = requests.get(check["endpointUrl"])
response.encoding = "utf-8"
test = {
"testResponseForText": test_response_for_text,
"testResponseAgainstRegex": test_response_against_regex,
"testResponseCode200": test_response_code_200,
}[check["test"]](response, check)
if response.status_code == 200 and test:
response_text = "✔ 200 Okay!"
print(
" " * (LINE_LENGTH - len(notice) - 2 - len(response_text)),
colored(response_text, "green", attrs=["bold"]),
)
return True
response_text = (
f"✘ {response.status_code} (content test {('failed', 'passed')[test]})"
)
except requests.exceptions.RequestException as exc:
response_text = (
" " * (LINE_LENGTH - len(notice) - 2 - len("✘ Failed!"))
+ "✘ Failed!\n"
+ textwrap.indent(
"\n".join(
textwrap.fill(line, LINE_LENGTH - 3) + ":"
for line in str(exc).split(":")
),
" > ",
)
)
print(
" " * (LINE_LENGTH - len(notice) - 2 - len(response_text)),
colored(response_text, "red", attrs=["bold"]),
)
return False
def do_section(checks):
heading = checks["heading"]
deco = "=" * int(LINE_LENGTH - len(heading) - 6)
print(colored(f"\n==== {heading} {deco}", "yellow", attrs=["bold"]))
return sum(not do_check(check) for check in checks["services"].values())
def main():
"""Command-line entry-point."""
parser = argparse.ArgumentParser(description="Description: {}".format(__file__))
parser.add_argument(
"-g",
"--get-config-from-github",
action="store_true",
default=False,
help="Get the config.js file from the GitHub repository",
)
args = parser.parse_args()
if args.get_config_from_github:
config_contents = get_config_from_github()
else:
with open("../src/config.js", "r") as _fh:
config_contents = _fh.read()
config = parse_config(config_contents)
if sum(do_section(checks) for checks in config.values()):
raise SystemExit(1)
raise SystemExit(0)
if __name__ == "__main__":
main()