Mini Shell
#!/opt/cloudlinux/venv/bin/python3 -bb
# coding=utf-8
#
# Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2020 All Rights Reserved
#
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENCE.TXT
#
import getopt
import os
import random
import sys
from sentry import init_wmt_sentry_client, setup_logger
__ALL__ = ["add_cron", "erase_cron", "remove_cron", "add_cron_task"]
WMT_CRONS = ['wmt-clickhouse-reporter', 'wmt-email-reporter']
def usage():
print('')
print('Use following syntax to manage WMT cron jobs install utility:')
print(sys.argv[0] + " [OPTIONS]")
print('Options:')
print(" -i | --install : install wmt cron jobs")
print(" -d | --delete : delete wmt cron jobs")
print(" -u | --update : update wmt cron jobs")
def add_cron(file_name, minute, hour, day, month, day_of_week, user, command,
check_command=True):
"""
Add new cron task into crontab schedule if this task or command wasn't already existed in the cron-file.
:param str file_name: Name of cron-file in /etc/cron.d
:param minute: Integer or char 'r' if to set random minute
:param hour: Integer or char 'r' if to set random hour
:param int, str day: Day number
:param int, str month: Month number
:param int, str day_of_week: Day of week number
:param str user: Under what user do run command
:param str command: What command do run
:param bool check_command: If it is False, check that whole cron-task line already exists in crontab,
check that command string exists instead. Default is True, check a command string.
"""
if minute == 'r':
minute = int(round(random.uniform(0, 59))) # pylint: disable=round-builtin
if hour == 'r':
hour = int(round(random.uniform(0, 23))) # pylint: disable=round-builtin
try:
cron_task = format_cron_task(minute, hour, day, month, day_of_week, user, command)
add_cron_task(file_name, cron_task, check_command)
except TypeError:
sys.stderr.write("Can not add task with wrong syntax")
def add_cron_task(file_name, task, check_command=False):
"""
Add new cron task in cron format if this task or command in this task wasn't already existed in the cron-file.
:param str file_name: Name of cron-file in /etc/cron.d
:param str task: Cron task in format "min hour day mon d_of_w user command"
:param bool check_command: If it is False, check that whole cron-task line already exists in crontab,
check that command string exists instead. Default is False, check a whole cron-task string.
"""
cron_file_path = os.path.join('/etc/cron.d/', file_name)
try:
content = ''
if os.path.exists(cron_file_path):
with open(cron_file_path) as f:
content = f.readlines()
if not is_in_cron(task, content, check_command):
with open(cron_file_path, 'a') as f:
f.write("%s\n" % task)
except (IOError, OSError):
return False
return True
def remove_cron(file_name):
"""
Remove cron-file from fs
:param str file_name: Name of cron-file in /etc/cron.d
"""
try:
os.remove(
os.path.join('/etc/cron.d/', file_name)
)
except (OSError, IOError):
pass
def erase_cron(file_name):
"""
Make cron-file empty
:param str file_name: Name of cron-file in /etc/cron.d
"""
f = None
try:
f = open(
os.path.join("/etc/cron.d/", file_name), "w"
)
except (IOError, OSError) as err:
sys.stderr.write("Can not erase crontab file %s because %s\n" % (
file_name, str(err)))
else:
f.close()
def format_cron_task(minute, hour, day, month, day_of_week, user, command):
"""
Build cron-task string in the cron format
:param minute: Integer or char 'r' if to set random minute
:param hour: Integer or char 'r' if to set random hour
:param int day: Day number
:param int month: Month number
:param int day_of_week: Day of week number
:param str user: Under what user do run command
:param str command: What command do run
:return: Cron-task in the cron format
:rtype: str
"""
arguments = (minute, hour, day, month, day_of_week, user, command)
for arg in arguments:
if arg is None:
raise TypeError("Wrong schedule for cron task")
return "%2s %2s %2s %2s %2s %10s %s" % arguments
def parse_cron_task(task):
"""
Split cron task string into cron task parts
:param str task: Cron-task string in the cron format
:return: List of cron-task parts
:rtype: list of str
"""
return task.split(None, 6)
def get_task_in_cron(crontab, get_parsed=False):
"""
Returns iterator through crontab tasks
:param iterable crontab: Iterator with crontab tasks' strings
:param bool get_parsed: If it is True, return crontab task as list of task's parts
return crontab task as a string instead
:return: Crontab task
:rtype: str
:rtype: list of str
"""
for cron_t in (s.strip() for s in crontab):
try:
if get_parsed:
t = parse_cron_task(cron_t)
else:
t = format_cron_task(*parse_cron_task(cron_t))
except TypeError:
sys.stderr.write("Wrong crontab task syntax: %s\n" % cron_t)
else:
yield t
def is_task_in_cron(task, cron_content):
"""
Find first occurence of task in cron-file if it has
:param str task: Cron-task in cront format to compare with
:param list cron_content: list of cron contents lines
:return: True if such a task is already existed in cron-file, False instead
:rtype: bool
"""
for t in get_task_in_cron(cron_content):
if t == task:
return True
return False
def is_command_in_cron(task, cron_content):
"""
:param str task: Task with command to looking for
:param list cron_content: list of cron content lines
:return: True if such a command is already existed in cron-file, False instead
:rtype: bool
Find first occurence of command in cron-file if it has
"""
command = parse_cron_task(task)[-1]
for t in get_task_in_cron(cron_content, get_parsed=True):
if t[-1] == command:
return True
return False
def is_in_cron(task, cron_content, check_command=False):
"""
Find first occurence of command or task in cron-file if it has
:param str task: Task or command to looking for
:param list cron_content: content of cron file
:param bool check_command: If it is True, check command occurence, check task occurence instead
:return: True if such a command or task is already existed in cron-file, False instead
:rtype: bool
"""
if check_command:
return is_command_in_cron(task, cron_content)
return is_task_in_cron(task, cron_content)
def is_valid_cron_task(t):
"""
Straight-forward approach to cron minute/hour validation
(our crons always have digits on minute/hour positions)
Better to clone croniter package for advanced validation
"""
if not t[0].isdigit() or int(t[0]) >= 60:
return False
if not t[1].isdigit() or int(t[1]) >= 24:
return False
return True
def get_cron_list():
return WMT_CRONS
def get_cron_params(cron_name):
"""
Get crontab entries for WMT cron jobs
"""
wmt_bin = '/usr/share/web-monitoring-tool/wmtbin'
wmt_api = os.path.join(wmt_bin, 'wmt-api')
logfile = '/var/log/cl_wmt.log'
if cron_name == 'wmt-clickhouse-reporter':
send_to_clickhouse = f'{wmt_api} --send-clickhouse'
return [
'r', 'r', '*', '*', '*', 'root',
f'/usr/bin/flock -n /var/run/wmt_clickhouse_report.cronlock {send_to_clickhouse} &>> {logfile}'
]
elif cron_name == 'wmt-email-reporter':
report_to_mail_cmd = f'{wmt_api} --send-email'
return [
'0', '0', '*', '*', '*', 'root',
f'/usr/bin/flock -n /var/run/wmt_email_report.cronlock {report_to_mail_cmd} &>> {logfile}'
]
elif cron_name == 'wmt-file-rotator':
rotate_wmt_files = '-name "wmt-db-*.sqlite" -delete -or -name "wmt_report*.json" -delete'
linux_find = f'/usr/bin/find /var/lve/wmt/ -maxdepth 1 -mtime +7 {rotate_wmt_files}'
return [
'0', '0', '*', '*', '*', 'root',
'/usr/bin/flock -n /var/run/wmt_file_rotator.cronlock ' + linux_find
]
else:
raise ValueError('Invalid cron name: %s', cron_name)
def install_crons():
for cron_name in get_cron_list():
add_cron(cron_name, *get_cron_params(cron_name))
def delete_crons():
for cron_name in get_cron_list():
remove_cron(cron_name)
def update_crons():
for cron_name in get_cron_list():
cron_file_path = os.path.join('/etc/cron.d/', cron_name)
if not os.path.exists(cron_file_path):
continue
# always update cron file with actual tasks
remove_cron(cron_name)
add_cron(cron_name, *get_cron_params(cron_name))
if __name__ == "__main__":
logger = setup_logger('cron_control')
init_wmt_sentry_client()
try:
opts, args = getopt.getopt(
sys.argv[1:],
"hidu",
["help", "postupcp", "install", "delete", "update"]
)
except getopt.GetoptError as err:
# print help information and exit:
print(str(err)) # will print something like "option -a not recognized"
usage()
sys.exit(2)
try:
for o, a in opts:
if o in ("-h", "--help"):
usage()
sys.exit()
elif o in ("-i", "--install"):
install_crons()
elif o in ("-d", "--delete"):
delete_crons()
elif o in ("-u", "--update"):
update_crons()
else:
usage()
sys.exit(2)
except Exception as e:
logger.exception(e)
Zerion Mini Shell 1.0