107 lines
3.0 KiB
Python
Executable File
107 lines
3.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Generic job runner for scheduled tasks in Docker containers.
|
|
|
|
Runs a Flask CLI command on a configurable interval with graceful shutdown support.
|
|
|
|
Environment Variables:
|
|
JOB_NAME: Name of the job to run (zerotier_reconciliation, mfa_compliance)
|
|
JOB_INTERVAL_SECONDS: Seconds between job runs (default: 300)
|
|
|
|
Usage:
|
|
docker run -e JOB_NAME=zerotier_reconciliation -e JOB_INTERVAL_SECONDS=120 app
|
|
"""
|
|
|
|
import os
|
|
import signal
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
import logging
|
|
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format="%(asctime)s [%(levelname)s] %(message)s",
|
|
datefmt="%Y-%m-%d %H:%M:%S",
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
JOB_COMMANDS = {
|
|
"zerotier_reconciliation": "python manage.py run_zerotier_reconciliation",
|
|
"mfa_compliance": "python manage.py run_mfa_compliance_job",
|
|
"cleanup_sessions": "python manage.py cleanup_sessions",
|
|
}
|
|
|
|
shutdown_requested = False
|
|
|
|
|
|
def signal_handler(signum, frame):
|
|
global shutdown_requested
|
|
logger.info(f"Received signal {signum}, initiating graceful shutdown...")
|
|
shutdown_requested = True
|
|
|
|
|
|
def run_job(job_name: str) -> bool:
|
|
command = JOB_COMMANDS.get(job_name)
|
|
if not command:
|
|
logger.error(f"Unknown job: {job_name}. Valid jobs: {list(JOB_COMMANDS.keys())}")
|
|
return False
|
|
|
|
logger.info(f"Running job: {job_name}")
|
|
start_time = time.monotonic()
|
|
|
|
try:
|
|
result = subprocess.run(
|
|
command,
|
|
shell=True,
|
|
cwd="/app",
|
|
capture_output=False,
|
|
)
|
|
elapsed = time.monotonic() - start_time
|
|
logger.info(f"Job {job_name} completed in {elapsed:.2f}s with exit code {result.returncode}")
|
|
return result.returncode == 0
|
|
except Exception as e:
|
|
elapsed = time.monotonic() - start_time
|
|
logger.error(f"Job {job_name} failed after {elapsed:.2f}s: {e}")
|
|
return False
|
|
|
|
|
|
def main():
|
|
job_name = os.getenv("JOB_NAME")
|
|
interval = int(os.getenv("JOB_INTERVAL_SECONDS", "300"))
|
|
|
|
if not job_name:
|
|
logger.error("JOB_NAME environment variable is required")
|
|
sys.exit(1)
|
|
|
|
if job_name not in JOB_COMMANDS:
|
|
logger.error(f"Unknown JOB_NAME: {job_name}. Valid: {list(JOB_COMMANDS.keys())}")
|
|
sys.exit(1)
|
|
|
|
if interval < 10:
|
|
logger.error(f"JOB_INTERVAL_SECONDS must be at least 10 seconds, got {interval}")
|
|
sys.exit(1)
|
|
|
|
signal.signal(signal.SIGTERM, signal_handler)
|
|
signal.signal(signal.SIGINT, signal_handler)
|
|
|
|
logger.info(f"Job runner started: {job_name}, interval={interval}s")
|
|
logger.info(f"Valid jobs: {list(JOB_COMMANDS.keys())}")
|
|
|
|
while not shutdown_requested:
|
|
run_job(job_name)
|
|
|
|
if shutdown_requested:
|
|
break
|
|
|
|
logger.info(f"Sleeping for {interval}s until next run...")
|
|
|
|
sleep_start = time.monotonic()
|
|
while time.monotonic() - sleep_start < interval and not shutdown_requested:
|
|
time.sleep(1)
|
|
|
|
logger.info("Job runner stopped")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|