Skip to content

Commit 740f230

Browse files
Adding Support to read LB_JOB_SCHEDULE for cadence (#1135)
Merging
1 parent e88cfba commit 740f230

File tree

1 file changed

+97
-2
lines changed

1 file changed

+97
-2
lines changed

unskript-ctl/unskript_ctl_config_parser.py

Lines changed: 97 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def __init__(
7171
self.connectors = connectors
7272
self.custom_scripts = custom_scripts
7373
self.notify = notify
74-
74+
7575

7676
def parse(self):
7777
cmds = []
@@ -267,6 +267,75 @@ def configure_credential(self):
267267
print(f'{bcolors.FAIL}Credential: Failed to program {cred_type}, name {name}{bcolors.ENDC}')
268268
continue
269269

270+
def validate_cron_format(self, cron_expression):
271+
"""Validate if a cron expression is in proper format
272+
Returns tuple: (is_valid: bool, error_message: str)
273+
"""
274+
if not cron_expression or not isinstance(cron_expression, str):
275+
return False, "Cron expression cannot be empty"
276+
277+
# Remove extra whitespace and split
278+
parts = cron_expression.strip().split()
279+
280+
# Standard cron should have 5 parts: minute hour day month day_of_week
281+
if len(parts) != 5:
282+
return False, f"Cron expression must have exactly 5 parts (minute hour day month day_of_week), got {len(parts)}: {cron_expression}"
283+
284+
# Define valid ranges for each field
285+
field_ranges = [
286+
(0, 59, "minute"), # minute: 0-59
287+
(0, 23, "hour"), # hour: 0-23
288+
(1, 31, "day"), # day: 1-31
289+
(1, 12, "month"), # month: 1-12
290+
(0, 7, "day_of_week") # day_of_week: 0-7 (0 and 7 are Sunday)
291+
]
292+
293+
for i, (part, (min_val, max_val, field_name)) in enumerate(zip(parts, field_ranges)):
294+
if not self._validate_cron_field(part, min_val, max_val, field_name):
295+
return False, f"Invalid {field_name} field: '{part}' (should be {min_val}-{max_val} or valid cron syntax)"
296+
297+
return True, "Valid cron expression"
298+
299+
def _validate_cron_field(self, field, min_val, max_val, field_name):
300+
"""Validate individual cron field"""
301+
# Allow wildcards
302+
if field == "*":
303+
return True
304+
305+
# Allow step values (*/5, */10, etc.)
306+
if field.startswith("*/"):
307+
try:
308+
step = int(field[2:])
309+
return step > 0 and step <= max_val
310+
except ValueError:
311+
return False
312+
313+
# Allow ranges (1-5, 10-15, etc.)
314+
if "-" in field:
315+
try:
316+
start, end = field.split("-", 1)
317+
start_num = int(start)
318+
end_num = int(end)
319+
return (min_val <= start_num <= max_val and
320+
min_val <= end_num <= max_val and
321+
start_num <= end_num)
322+
except ValueError:
323+
return False
324+
325+
# Allow comma-separated lists (1,3,5 or 10,20,30, etc.)
326+
if "," in field:
327+
try:
328+
values = [int(x.strip()) for x in field.split(",")]
329+
return all(min_val <= val <= max_val for val in values)
330+
except ValueError:
331+
return False
332+
333+
# Allow single numbers
334+
try:
335+
num = int(field)
336+
return min_val <= num <= max_val
337+
except ValueError:
338+
return False
270339

271340

272341
def configure_schedule(self):
@@ -280,14 +349,40 @@ def configure_schedule(self):
280349
print(f"{bcolors.WARNING}Scheduler: No scheduler configuration found{bcolors.ENDC}")
281350
return
282351

352+
# Check for LB_JOB_SCHEDULE environment variable
353+
lb_job_schedule = os.environ.get('LB_JOB_SCHEDULE')
354+
if lb_job_schedule:
355+
print(f'{bcolors.OKGREEN}Found LB_JOB_SCHEDULE environment variable: {lb_job_schedule}{bcolors.ENDC}')
356+
357+
# Validate the cron format
358+
is_valid, validation_message = self.validate_cron_format(lb_job_schedule)
359+
if not is_valid:
360+
print(f'{bcolors.FAIL}ERROR: LB_JOB_SCHEDULE has invalid cron format: {validation_message}{bcolors.ENDC}')
361+
print(f'{bcolors.FAIL}Examples of valid cron formats:{bcolors.ENDC}')
362+
print(f'{bcolors.FAIL} */15 * * * * (every 15 minutes){bcolors.ENDC}')
363+
print(f'{bcolors.FAIL} 0 */2 * * * (every 2 hours){bcolors.ENDC}')
364+
print(f'{bcolors.FAIL} 30 9 * * 1-5 (9:30 AM, Monday to Friday){bcolors.ENDC}')
365+
print(f'{bcolors.FAIL} 0 0 1 * * (first day of every month){bcolors.ENDC}')
366+
print(f'{bcolors.FAIL}Falling back to YAML configuration cadence values{bcolors.ENDC}')
367+
lb_job_schedule = None # Disable override
368+
else:
369+
print(f'{bcolors.OKGREEN}LB_JOB_SCHEDULE validation passed: {validation_message}{bcolors.ENDC}')
370+
print(f'{bcolors.OKGREEN}This will override all cadence values in the scheduler configuration{bcolors.ENDC}')
371+
372+
283373
unskript_crontab_file = "/etc/unskript/unskript_crontab.tab"
284374
crons = []
285375
try:
286376
for schedule in config:
287377
if schedule.get('enable') is False:
288378
print(f'Skipping')
289379
continue
290-
cadence = schedule.get('cadence')
380+
if lb_job_schedule:
381+
cadence = lb_job_schedule
382+
print(f"{bcolors.OKGREEN}Using LB_JOB_SCHEDULE override: {cadence}{bcolors.ENDC}")
383+
else:
384+
cadence = schedule.get('cadence')
385+
291386
job_name = schedule.get('job_name')
292387
# look up the job name and get the commands
293388
job = self.jobs.get(job_name)

0 commit comments

Comments
 (0)