Commit 66c0d5cb authored by Bram Daams's avatar Bram Daams
Browse files

Merge branch '11-sch-should-always-run-supplied-command' into 'master'

Resolve "sch should always run supplied command"

Closes #11

See merge request !11
parents 32988118 b1f8cdea
Pipeline #37648 passed with stage
in 57 seconds
......@@ -32,12 +32,13 @@ INTERVAL_DICT = collections.OrderedDict([
class Cron():
"""
Cron returns a list of system jobs based on a filter
Cron searches for cron jobs with the environment variable
"JOB_ID={job_id}" for given job_id
"""
# pylint: disable=too-few-public-methods
def __init__(self, job_id=''):
def __init__(self, job_id):
self._jobs = []
self._job_id = job_id
command_filter = "JOB_ID={} ".format(job_id)
crontabs = CronTabs().all.find_command(command_filter)
......@@ -45,11 +46,26 @@ class Cron():
if crontab.enabled:
self._jobs.append(Job(crontab))
def jobs(self):
def job(self):
"""
returns list of Jobs found on the system
returns the matching cron job
or None if there are no or multiple matches or
if given job_id was None to start with
"""
return self._jobs
if not self._job_id:
return None
if len(self._jobs) == 1:
return self._jobs[0]
logging.error(
'found %s matching cron jobs for given job id'
'. 1 expected (job.id=%s)',
len(self._jobs),
self._job_id
)
return None
class Job():
......
......@@ -13,31 +13,14 @@ from ttictoc import TicToc
from hc import Cron, HealthcheckCredentials, Healthchecks
CONFIG = configparser.ConfigParser()
try:
CONFIG.read(['sch.conf', '/etc/sch.conf'])
URL = CONFIG.get('hc', 'healthchecks_api_url')
KEY = CONFIG.get('hc', 'healthchecks_api_key')
except configparser.Error:
sys.exit(
'ERROR: Could not find/read/parse config'
'file sch.conf or /etc/sch.conf'
)
CRED = HealthcheckCredentials(
api_url=URL,
api_key=KEY
)
HANDLER = logging.handlers.SysLogHandler('/dev/log')
FORMATTER = logging.Formatter(
'{name}/%(module)s.%(funcName)s: %(message)s'.format(name=__name__)
'{name}/%(module)s.%(funcName)s:'
'%(levelname)s %(message)s'.format(name=__name__)
)
HANDLER.setFormatter(FORMATTER)
ROOT = logging.getLogger()
# log level DEBUG
ROOT.setLevel(logging.DEBUG)
ROOT.addHandler(HANDLER)
......@@ -56,6 +39,22 @@ def execute_shell_command(command):
return exit_code
def get_job_id(command):
"""
returns the value of the JOB_ID environment variable in specified
command string
returns None if not found
"""
regex = r".*JOB_ID=([\w,-]*)"
match = re.match(regex, command)
if match:
return match.group(1)
logging.debug("Could not find JOB_ID in command %s", command)
return None
def run():
"""
sch:run is a cron shell that registers, updates and pings cron jobs in
......@@ -70,84 +69,114 @@ def run():
If you want to set additional tags for your check, you should do that with
an environment variable JOB_TAGS. Seperate multiple tags with a comma.
"""
# pylint:disable=too-many-statements
# we should have excactly two arguments
if len(sys.argv) != 3:
# cron runs sch with two arguments
logging.error("Expected two arguments")
sys.exit("Error: Expected two arguments")
# first argument should be '-c'
if sys.argv[1] != '-c':
# cron runs the shell with the -c flag
logging.error("The first argument should be '-c'")
sys.exit("Error: the first argument should be '-c'")
# cron command (including env variable JOB_ID) is the 2nd argument
command = sys.argv[2]
job_id = get_job_id(command)
# determine JOB_ID
regex = r".*JOB_ID=([\w,-]*)"
match = re.match(regex, command)
if not match:
# find system cron job that executes this command
job = Cron(job_id).job()
# try loading Healthchecks API url and key
try:
config = configparser.ConfigParser()
config.read(['sch.conf', '/etc/sch.conf'])
url = config.get('hc', 'healthchecks_api_url')
key = config.get('hc', 'healthchecks_api_key')
cred = HealthcheckCredentials(
api_url=url,
api_key=key
)
try_hc = True
except configparser.Error:
logging.error(
'Could not find/read/parse config'
'file sch.conf or /etc/sch.conf'
)
try_hc = False
if try_hc:
# we do have the API url/key from the configuration
# lets try to communicate with Healthchecks
# pylint:disable=broad-except
try:
health_checks = Healthchecks(cred)
except Exception:
logging.error('Could not connect to Healthchecks')
try_hc = False
# pylint:enable=broad-except
if not job_id or not try_hc or not job:
# for some reason, we can't do much with Healthchecks
# at this point. So, we run the job without too much SCH
# interference
logging.debug(
"running a job without a JOB_ID, so no "
"associated check, command: %s",
"Running a job without SCH interference, command: %s",
command
)
execute_shell_command(command)
sys.exit()
# find system cron job that executes this command
job_id = match.group(1)
jobs = Cron(job_id).jobs()
if len(jobs) != 1:
# oops
sys.exit()
job = jobs[0]
check = None
is_new_check = False
# at this point, we're setup to do some smart stuff ;-)
# we know the exact cron configration for the job
# and we already communicated successfully with the
# configured Healthchecks instance
health_checks = Healthchecks(CRED)
check = health_checks.find_check(job)
if check:
logging.debug(
"found check for cron job (job.id=%s)",
job.id,
)
is_new_check = False
health_checks.update_check(check, job)
else:
logging.debug(
"found new cron job (job.id=%s)",
"no check found for cron job (job.id=%s)",
job.id,
)
is_new_check = True
check = health_checks.new_check(job)
if not check:
logging.error(
"could not find or register check for given command (job.id=%s)",
job.id,
)
if not check:
logging.error(
"Could not find or register check for given command. "
"Using read-only API keys? (job.id=%s)",
job.id,
)
# ping start
health_checks.ping(check, '/start')
timer = TicToc()
timer.tic()
# execute command
logging.debug(
"About to run command: %s (job.id=%s)",
"Executing shell commmand: %s (job.id=%s)",
command,
job.id,
)
exit_code = execute_shell_command(command)
timer.toc()
logging.debug(
"Command completed in %s seconds (job.id=%s)",
timer.elapsed,
......
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