Commit df2281a8 authored by Bram Daams's avatar Bram Daams
Browse files

geen registry, maar hashes en id's in tags van hc opslaan

parent 7ee5ed0a
Pipeline #36608 passed with stage
in 51 seconds
......@@ -15,20 +15,17 @@ For now, create a file `test.py`
from hc import hc, hcCred, hcRegistry
cred = hcCred('https://hc.example.com/api/v1/', 'mysecretapikey')
registry = hcRegistry(cred, 'doc/hcregistry.json')
h = hc(cred)
h.print_status()
h = Healthchecks(cred)
h.PrintStatus()
# scan jobs that want to use SCH
jobs = CronTabs().all.find_command('JOB_ID')
for job in jobs:
print("job:")
print(job)
hc_id = registry.get_id(job)
print(hc_id)
print("--------------")
check = h.FindCheck(job)
if check:
h.UpdateCheck(check, job)
else:
h.NewCheck(job)
```
And run it within the virtual environment
......
"""
module for interfacing a healthchecks.io compatible service
"""
import sys
import click
import arrow
import json
import hashlib
import platform
import re
......@@ -10,6 +12,9 @@ import tzlocal
class hcCred:
"""
Object to store the healthchecks api url and key
"""
def __init__(self, url, api_key):
self.url = url
self.api_key = api_key
......@@ -19,110 +24,163 @@ class hcCred:
to {url}""".format(url=self.url)
class hcRegistry:
def __init__(self, cred, registry):
self.registry = registry
self.hc = hc(cred)
# read file
try:
with open(self.registry, 'r') as myfile:
data = myfile.read()
class Healthchecks:
def __init__(self, cred):
self.cred = cred
self.auth_headers = {'X-Api-Key': self.cred.api_key}
self.checks = self.get_checks()
# parse file
self.data = json.loads(data)
except Exception:
print("could not load registry")
def get_checks(self):
"""Returns a list of checks from the HC API"""
url = "{}checks/".format(self.cred.url)
def get_hash(self, job):
"""Returns the unique hash for given cron job"""
md5 = hashlib.md5()
md5.update(platform.node().encode('utf-8'))
md5.update(str(job.slices).encode('utf-8'))
md5.update(job.command.encode('utf-8'))
return md5.hexdigest()
try:
response = requests.get(url, headers=self.auth_headers)
response.raise_for_status()
except requests.exceptions.HTTPError as err:
print(err)
sys.exit(1)
if response:
return response.json()['checks']
def get_jobid(self, job):
"""Returns the value of environment variable JOB_ID if specified in the cron job"""
regex = r".*JOB_ID=(\w*)"
match = re.match(regex, job.command)
if match:
return match.group(1)
raise Exception('fetching cron checks failed')
def get_tags(self, job):
"""Returns the tags specified in the environment variable JOB_TAGS in the cron job"""
def FindCheck(self, job):
"""
Find a check in Healthchecks for the host and given job
"""
job_id = self.GetJobId(job)
tag_for_job = 'job_id={job_id}'.format(job_id=job_id)
tag_for_host = 'host={hostname}'.format(hostname=platform.node())
# see if there's a check with tags matching both this host
# and the job_id
for check in self.checks:
found_job = False
found_host = False
for tag in check['tags'].split(' '):
if tag == tag_for_job:
found_job = True
elif tag == tag_for_host:
found_host = True
if found_job and found_host:
return check
return None
def GetJobTags(self, job):
"""
Returns the tags specified in the environment variable
JOB_TAGS in the cron job
"""
regex = r'.*JOB_TAGS=([\w,]*)'
m = re.match(regex, job.command)
if m:
return m.group(1).replace(',', ' ')
return ""
def find_by_hash(self, job):
"""Find a job in the registry by hash"""
h = self.get_hash(job)
return next((elem for elem in self.data if elem['hash'] == h), False)
def GetJobId(self, job):
"""
Returns the value of environment variable JOB_ID if specified
in the cron job
"""
regex = r".*JOB_ID=(\w*)"
match = re.match(regex, job.command)
if match:
return match.group(1)
def find_by_jobid(self, job):
"""Find a job in the registry by job_id"""
j = self.get_jobid(job)
return next((elem for elem in self.data if elem['JOB_ID'] == j), False)
return None
def get_id(self, job):
"""Get the HC id for the given cron job"""
r = self.find_by_hash(job)
if r:
return r['HC_ID']
def GenerateJobHash(self, job):
"""Returns the unique hash for given cron job"""
md5 = hashlib.md5()
md5.update(platform.node().encode('utf-8'))
md5.update(str(job.slices).encode('utf-8'))
md5.update(job.command.encode('utf-8'))
return md5.hexdigest()
r = self.find_by_jobid(job)
if r:
# hash has changed, let's update the details
self.hc.update_check(r, job, self.get_tags(job))
return r['HC_ID']
def GetCheckHash(self, check):
regex = r"hash=(\w*)"
hash_search = re.search(regex, check['tags'])
return False
if hash_search:
return hash_search.group(1)
def register(self, id):
pass
return None
def UpdateCheck(self, check, job):
job_hash = self.GenerateJobHash(job)
check_hash = self.GetCheckHash(check)
class hc:
def __init__(self, cred):
self.cred = cred
self.auth_headers = {'X-Api-Key': self.cred.api_key}
if check_hash:
if job_hash == check_hash:
# hash did not change: no need to update checks' details
return True
def get_checks(self):
"""Returns a list of checks from the HC API"""
url = "{}checks/".format(self.cred.url)
# let's really update the check
print("about to update check:", check)
url = check['update_url']
# gather all the jobs' metadata
data = {
'schedule': job.slices.render(),
'desc': job.comment,
'grace': 3600,
'tz': tzlocal.get_localzone().zone,
'tags': 'sch host={host} job_id={job_id} '
'hash={hash} {tags}'.format(
host=platform.node(),
job_id=self.GetJobId(job),
hash=job_hash,
tags=self.GetJobTags(job)
)
}
# post the data
try:
response = requests.get(url, headers=self.auth_headers)
response = requests.post(
url=url,
headers=self.auth_headers,
json=data
)
response.raise_for_status()
except requests.exceptions.HTTPError as err:
print("ERROR")
print(err)
sys.exit(1)
if response:
return response.json()['checks']
return False
raise Exception('fetching cron checks failed')
return True
def update_check(self, registration, job, tags):
url = "{apiurl}checks/{code}".format(
apiurl=self.cred.url,
code=registration['HC_ID']
)
def NewCheck(self, job):
job_hash = self.GenerateJobHash(job)
# gather all the jobs' metadata
data = {
'schedule': job.slices.render(),
'desc': job.comment,
'grace': 3600,
'tz': tzlocal.get_localzone().zone,
'tags': 'sch host_{} {}'.format(platform.node(), tags)
}
'name': 'new check',
'schedule': job.slices.render(),
'desc': job.comment,
'grace': 3600,
'channels': '*', # all available notification channels
'tz': tzlocal.get_localzone().zone,
'tags': 'sch host={host} job_id={job_id} '
'hash={hash} {tags}'.format(
host=platform.node(),
job_id=self.GetJobId(job),
hash=job_hash,
tags=self.GetJobTags(job)
)
}
# post the data
try:
response = requests.post(
url=url,
headers=self.auth_headers,
json=data
)
url='{}/checks/'.format(self.cred.url),
headers=self.auth_headers,
json=data
)
response.raise_for_status()
except requests.exceptions.HTTPError as err:
......@@ -130,11 +188,11 @@ class hc:
print(err)
return False
print('check created')
return True
def print_status(self, status_filter=""):
def PrintStatus(self, status_filter=""):
"""Show status of monitored cron jobs"""
checks = self.get_checks()
click.secho("{status:<6} {last_ping:<15} {name:<40}".format(
status="Status",
name="Name",
......@@ -146,7 +204,7 @@ class hc:
last_ping=""
))
for i in checks:
for i in self.checks:
if status_filter and i['status'] != status_filter:
continue
......
......@@ -9,5 +9,6 @@ setup(
'click',
'python-crontab',
'requests',
'tzlocal',
],
)
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment