I had quite some issue with unproperly formatted CRON.
I wrote a script to bring them to light and fix them.
Here it is:
#!/usr/bin/env python3
import os
import re
import sys
# ANSI escape code for red text
RED = '\033[31m'
GREEN = '\033[32m'
RESET = '\033[0m'
quartz_cron_regex = r"""
^\s*(
($|\#|\w+\s*=| # Empty, comment, or environment variable
(\?|\*| # Match ? or *
(?:[0-5]?\d)(?:(?:-|\/|,)(?:[0-5]?\d))? # Match seconds or minutes
(?:,(?:[0-5]?\d)(?:(?:-|\/|,)(?:[0-5]?\d))?)*)\s+ # Allow lists of ranges
(\?|\*| # Minutes field
(?:[0-5]?\d)(?:(?:-|\/|,)(?:[0-5]?\d))?
(?:,(?:[0-5]?\d)(?:(?:-|\/|,)(?:[0-5]?\d))?)*)\s+ # Allow lists of ranges
(\?|\*| # Hours field
(?:[01]?\d|2[0-3])(?:(?:-|\/|,)(?:[01]?\d|2[0-3]))?
(?:,(?:[01]?\d|2[0-3])(?:(?:-|\/|,)(?:[01]?\d|2[0-3]))?)*)\s+ # Lists and ranges
(\?|\*| # Day of month
(?:0?[1-9]|[12]\d|3[01])(?:(?:-|\/|,)(?:0?[1-9]|[12]\d|3[01]))?
(?:,(?:0?[1-9]|[12]\d|3[01])(?:(?:-|\/|,)(?:0?[1-9]|[12]\d|3[01]))?)*)\s+
(\?|\*| # Month field
(?:[1-9]|1[012])(?:(?:-|\/|,)(?:[1-9]|1[012]))?
(?:L|W)?(?:,(?:[1-9]|1[012])(?:(?:-|\/|,)(?:[1-9]|1[012]))?(?:L|W)?)*
|\?|\*|(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)
(?:(?:-)(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?
(?:,(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)
(?:(?:-)(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?)*)\s+
(\?|\*| # Day of week
(?:[0-6])(?:(?:-|\/|,|\#)(?:[0-6]))?
(?:L)?(?:,(?:[0-6])(?:(?:-|\/|,|\#)(?:[0-6]))?(?:L)?)*
|\?|\*|(?:MON|TUE|WED|THU|FRI|SAT|SUN)
(?:(?:-)(?:MON|TUE|WED|THU|FRI|SAT|SUN))?
(?:,(?:MON|TUE|WED|THU|FRI|SAT|SUN)
(?:(?:-)(?:MON|TUE|WED|THU|FRI|SAT|SUN))?)*)) # Fix parentheses for day of week
(|\s)+ # Optional whitespace
(\?|\*| # Year field
(?:\d{4})(?:(?:-|\/|,)(?:\d{4}))?
(?:,(?:\d{4})(?:(?:-|\/|,)(?:\d{4}))?)*)?
)$
"""
quartz_cron_pattern = re.compile(quartz_cron_regex, re.VERBOSE)
# Function to validate Quartz cron expressions
def validate_cron(cron_expr):
if len(cron_expr.split()) == 7 and quartz_cron_pattern.match(cron_expr):
return True
else:
return False
# Function to search for "Time cron" in files and validate cron expressions
def search_and_validate_crons(root_dir='.'):
# Regex to find lines containing 'Time cron' and extract cron expression
cron_pattern = re.compile(r'Time cron\s+"([^"]+)"')
err_count = 0
total_count = 0
# Walk through all files in the given directory
for root, dirs, files in os.walk(root_dir):
rule_files = [f for f in files if f.endswith('.rules')]
for file in rule_files:
file_path = os.path.join(root, file)
# Read file and search for cron expressions
with open(file_path, 'r', encoding='utf-8') as f:
for line_number, line in enumerate(f, start=1):
# Search for cron expressions
match = cron_pattern.search(line)
if match:
cron_expr = match.group(1)
# Validate the cron expression
total_count += 1
if validate_cron(cron_expr):
print(f"{file_path}:{line_number}\t: {GREEN}OK{RESET}\t{cron_expr}")
else:
print(f"{file_path}:{line_number}\t: {RED}ERR{RESET}\t{cron_expr}")
err_count += 1
if err_count > 0:
print(f"\n{RED}ERROR{RESET}: {err_count}/{total_count} Quartz cron expressions are invalid")
sys.exit(1) # Exit with a non-zero status
else:
print(f"\n{GREEN}SUCCESS{RESET}: {total_count}/{total_count} valid Quartz cron expressions")
sys.exit(0)
# Run the script
if __name__ == "__main__":
search_and_validate_crons()
Copy it in your rules folder and chmod a+x check-cron.py
.
You may then run ./check-cron.py
. The output looks like:
./astro.rules:37 : OK - 0 0 8 * * *
./astro.rules:49 : OK - 0 00 20 * * *
./astro.rules:97 : NOT OK - 0 0 19 ? * *
./astro.rules:105 : NOT OK - 0 0 6 ? * *
[...]
ERROR: 31/45 cron expressions are invalid
With errors showing up in red.
If you run that from VScode, you may click on the file ref and VSCode will jump to the affected line, helping you to fix those quickly