diff --git a/pyproject.toml b/pyproject.toml index b885974..c49a133 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ dependencies = [ "httpx>=0.28.1", "ms-active-directory>=1.14.1", "tomlkit>=0.13.2", + "python-crontab>=3.2.0", "validators>=0.34.0", ] diff --git a/src/adgroupsync/conf.py b/src/adgroupsync/conf.py index 0f28a2f..0693472 100644 --- a/src/adgroupsync/conf.py +++ b/src/adgroupsync/conf.py @@ -2,6 +2,8 @@ import argparse import logging import os from pathlib import Path +from crontab import CronTab, CronSlices +import shutil import pytz import tomlkit @@ -82,34 +84,67 @@ def create_config_file(dest: Path): # Write configuration file TOMLFile(dest).write(conf) + +def create_cron_job(cron_job: str, config_file: Path): + # Check if the script exists and its executable + if not shutil.which("test1"): + print( + f"No executable found, please add this to your crontab manually: '/path/to/adgroupsync --conf {config_file} >/dev/null 2>&1'" + ) + + # Checking if the string is valid + if not CronSlices.is_valid(cron_job): + raise Exception(f"Cron job '{cron_job}' is not valid.") + + # Creating the cron job + cron = CronTab(user=True) + job = cron.new(command=f"adgroupsync --conf {config_file} >/dev/null 2>&1") + job.setall(cron_job) + cron.write() + + # Assign environment variables or configuration file values -AD_DOMAIN = os.getenv('AD_DOMAIN') -AD_USER_NAME = os.getenv('AD_USER') -AD_PASSWORD = os.getenv('AD_PASSWORD') -AD_LDAP_SERVER = [s.strip() for s in os.getenv('AD_LDAP_SERVER').split(',')] \ - if os.getenv('AD_LDAP_SERVER') is not None else None -AD_TIMEZONE = pytz.timezone(os.getenv('AD_TIMEZONE')) \ - if os.getenv('AD_TIMEZONE') else None -AD_PARENT_GROUP = os.getenv('AD_PARENT_GROUP') -STDOUT_LOG_LEVEL = os.getenv('STDOUT_LOG_LEVEL') -FILE_LOG_LEVEL = os.getenv('FILE_LOG_LEVEL') -LOG_DIR = os.getenv('LOG_DIR') -CIVICRM_BASE_URL = os.getenv('CIVICRM_BASE_URL') -CIVICRM_API_KEY = os.getenv('CIVICRM_API_KEY') -CIVICRM_BATCH_SIZE = int(os.getenv('CIVICRM_BATCH_SIZE')) \ - if os.getenv('CIVICRM_BATCH_SIZE') is not None else None -CIVICRM_RETRIES = int(os.getenv('CIVICRM_RETRIES')) \ - if os.getenv('CIVICRM_RETRIES') is not None else None -CIVICRM_IGNORE_SSL = bool(os.getenv('CIVICRM_IGNORE_SSL')) \ - if os.getenv('CIVICRM_IGNORE_SSL') is not None else None -NTFY_URL = os.getenv('NTFY_URL') -NTFY_TOPIC = os.getenv('NTFY_TOPIC') -NTFY_ACCESS_TOKEN = os.getenv('NTFY_ACCESS_TOKEN') +AD_DOMAIN = os.getenv("AD_DOMAIN") +AD_USER_NAME = os.getenv("AD_USER") +AD_PASSWORD = os.getenv("AD_PASSWORD") +AD_LDAP_SERVER = ( + [s.strip() for s in os.getenv("AD_LDAP_SERVER").split(",")] + if os.getenv("AD_LDAP_SERVER") is not None + else None +) +AD_TIMEZONE = ( + pytz.timezone(os.getenv("AD_TIMEZONE")) if os.getenv("AD_TIMEZONE") else None +) +AD_PARENT_GROUP = os.getenv("AD_PARENT_GROUP") +STDOUT_LOG_LEVEL = os.getenv("STDOUT_LOG_LEVEL") +FILE_LOG_LEVEL = os.getenv("FILE_LOG_LEVEL") +LOG_DIR = os.getenv("LOG_DIR") +CIVICRM_BASE_URL = os.getenv("CIVICRM_BASE_URL") +CIVICRM_API_KEY = os.getenv("CIVICRM_API_KEY") +CIVICRM_BATCH_SIZE = ( + int(os.getenv("CIVICRM_BATCH_SIZE")) + if os.getenv("CIVICRM_BATCH_SIZE") is not None + else None +) +CIVICRM_RETRIES = ( + int(os.getenv("CIVICRM_RETRIES")) + if os.getenv("CIVICRM_RETRIES") is not None + else None +) +CIVICRM_IGNORE_SSL = ( + bool(os.getenv("CIVICRM_IGNORE_SSL")) + if os.getenv("CIVICRM_IGNORE_SSL") is not None + else None +) +NTFY_URL = os.getenv("NTFY_URL") +NTFY_TOPIC = os.getenv("NTFY_TOPIC") +NTFY_ACCESS_TOKEN = os.getenv("NTFY_ACCESS_TOKEN") try: argparser = argparse.ArgumentParser( - description='This program synchronizes Active Directory groups with ' - 'CiviCRM groups.') + description="This program synchronizes Active Directory groups with " + "CiviCRM groups." + ) argparser.add_argument( "--conf", @@ -124,6 +159,12 @@ try: help="Create a configuration file", ) + argparser.add_argument( + "--create-cron", + action="store", + help="Create a cron job", + ) + args = argparser.parse_args() # If a path to a config file was provided @@ -133,44 +174,53 @@ try: config_file = Path(args.conf) if not config_file.is_file() and not args.create_conf: raise FileNotFoundError( - f"Configuration file '{config_file}' does not exist.") + f"Configuration file '{config_file}' does not exist." + ) # Create configuration file if requested and exit if args.create_conf: create_config_file(config_file) exit(0) + # Create the crob job if requested and exit + if args.create_cron: + cron_job = args.create_cron + create_cron_job(cron_job, config_file) + exit(0) + # Load configuration file config = TOMLFile(config_file).read() # Get values from configuration file - AD_DOMAIN = AD_DOMAIN or config['AD']['DOMAIN'] - AD_USER_NAME = AD_USER_NAME or config['AD']['USER'] - AD_PASSWORD = AD_PASSWORD or config['AD']['PASSWORD'] - AD_LDAP_SERVER = AD_LDAP_SERVER or config['AD'].get('LDAP_SERVER') - AD_TIMEZONE = AD_TIMEZONE \ - or pytz.timezone(config['AD'].get('TIMEZONE', 'UTC')) - AD_PARENT_GROUP = AD_PARENT_GROUP or config['AD']['PARENT_GROUP'] - STDOUT_LOG_LEVEL = STDOUT_LOG_LEVEL \ - or config['LOGGING'].get('STDOUT_LOG_LEVEL', 'INFO') - FILE_LOG_LEVEL = FILE_LOG_LEVEL \ - or config['LOGGING'].get('FILE_LOG_LEVEL', 'WARNING') - LOG_DIR = LOG_DIR or config['LOGGING'].get('LOG_DIR') - CIVICRM_BASE_URL = CIVICRM_BASE_URL or config['CIVICRM']['BASE_URL'] - CIVICRM_API_KEY = CIVICRM_API_KEY or config['CIVICRM']['API_KEY'] - CIVICRM_BATCH_SIZE = CIVICRM_BATCH_SIZE \ - or config['CIVICRM']['BATCH_SIZE'] - CIVICRM_RETRIES = CIVICRM_RETRIES \ - or config['CIVICRM'].get('RETRIES', 3) - CIVICRM_IGNORE_SSL = CIVICRM_IGNORE_SSL \ - or bool(config['CIVICRM'].get('IGNORE_SSL', False)) - NTFY_URL = NTFY_URL or config['NTFY'].get( - 'URL') if 'NTFY' in config else None - NTFY_TOPIC = NTFY_TOPIC or config['NTFY'].get( - 'TOPIC') if 'NTFY' in config else None - NTFY_ACCESS_TOKEN = NTFY_ACCESS_TOKEN \ - or config['NTFY'].get( - 'ACCESS_TOKEN') if 'NTFY' in config else None + AD_DOMAIN = AD_DOMAIN or config["AD"]["DOMAIN"] + AD_USER_NAME = AD_USER_NAME or config["AD"]["USER"] + AD_PASSWORD = AD_PASSWORD or config["AD"]["PASSWORD"] + AD_LDAP_SERVER = AD_LDAP_SERVER or config["AD"].get("LDAP_SERVER") + AD_TIMEZONE = AD_TIMEZONE or pytz.timezone(config["AD"].get("TIMEZONE", "UTC")) + AD_PARENT_GROUP = AD_PARENT_GROUP or config["AD"]["PARENT_GROUP"] + STDOUT_LOG_LEVEL = STDOUT_LOG_LEVEL or config["LOGGING"].get( + "STDOUT_LOG_LEVEL", "INFO" + ) + FILE_LOG_LEVEL = FILE_LOG_LEVEL or config["LOGGING"].get( + "FILE_LOG_LEVEL", "WARNING" + ) + LOG_DIR = LOG_DIR or config["LOGGING"].get("LOG_DIR") + CIVICRM_BASE_URL = CIVICRM_BASE_URL or config["CIVICRM"]["BASE_URL"] + CIVICRM_API_KEY = CIVICRM_API_KEY or config["CIVICRM"]["API_KEY"] + CIVICRM_BATCH_SIZE = CIVICRM_BATCH_SIZE or config["CIVICRM"]["BATCH_SIZE"] + CIVICRM_RETRIES = CIVICRM_RETRIES or config["CIVICRM"].get("RETRIES", 3) + CIVICRM_IGNORE_SSL = CIVICRM_IGNORE_SSL or bool( + config["CIVICRM"].get("IGNORE_SSL", False) + ) + NTFY_URL = NTFY_URL or config["NTFY"].get("URL") if "NTFY" in config else None + NTFY_TOPIC = ( + NTFY_TOPIC or config["NTFY"].get("TOPIC") if "NTFY" in config else None + ) + NTFY_ACCESS_TOKEN = ( + NTFY_ACCESS_TOKEN or config["NTFY"].get("ACCESS_TOKEN") + if "NTFY" in config + else None + ) # Check if some required values are missing required = { @@ -183,11 +233,12 @@ try: "CIVICRM_API_KEY": CIVICRM_API_KEY, } if len(missing := [k for k, v in required.items() if v is None]) > 0: - raise ValueError('Some required values are missing. ' - 'Please use a configuration file ' - 'or provide all required environment variables. ' - 'Missing: %s' - % ','.join(missing)) + raise ValueError( + "Some required values are missing. " + "Please use a configuration file " + "or provide all required environment variables. " + "Missing: %s" % ",".join(missing) + ) except Exception as e: logger.error(e, exc_info=True)