Source code for src.config

"""Module that loads the tool's runtime configuration from a file and
makes it accessible to other modules."""

import logging
from configparser import ConfigParser
from datetime import datetime
from os import getenv
from pathlib import Path
from typing import Any, Dict, List, Optional, Type, TypeVar

logging.basicConfig(
    level=logging.INFO,
    format="[%(asctime)s] {%(filename)s:%(funcName)s:%(lineno)d} %(levelname)s - "
    "%(message)s",
    datefmt="%H:%M:%S",
)
logger: logging.Logger = logging.getLogger(__name__)


[docs] class Singleton:
[docs] def __new__(cls, *args: Any, **kw: Dict[str, Any]): if not hasattr(cls, "_instance"): orig = super(Singleton, cls) cls._instance = orig.__new__(cls, *args, **kw) return cls._instance
T = TypeVar("T") # Declare type variable
[docs] class Context(Singleton): """ Attributes: settings: mapping of detected runtime configuration options to their values. key naming scheme is <section>_<name> """ config_file_name: str = "occmdcfg.ini" config_file_locations: List[Path] = [ Path.cwd(), Path.cwd().parent, Path.home(), Path.home() / ".config/occmd/", Path.cwd()/ "examples/", # fall back to example config ]
[docs] def __init__(self): # there is a single timestamp per run self.timestamp: datetime = datetime.utcnow() # read config from file self.config: ConfigParser = self._read_config_file() # store options as attributes self.settings: Dict[str, Any] = {} self._set_config_attributes() # handle config values that are no strings self._transform_settings()
[docs] def _read_config_file(self) -> ConfigParser: for location in self.config_file_locations: config_file = location / self.config_file_name if config_file.exists() and config_file.is_file(): logger.info(f"Found config file in {location=})") config = ConfigParser() config.read(config_file.absolute().as_posix()) return config logger.info(f"Unable to find config file in {location=})") raise RuntimeError("Config file not found")
[docs] def _set_config_attributes(self) -> None: for section in self.config.sections(): for key, value in self.config[section].items(): config_name: str = f"{section}_{key}" env_val: Optional[str] = getenv(config_name.upper()) if env_val: logger.info(f"Config {config_name} was overridden by environment variable") value = env_val logger.info(f"Setting config option {config_name} = {value}") self.settings |= {f"{config_name}": value}
[docs] def _transform_settings(self) -> None: try: self._transform_setting("local_repo_db_resources_dir", Path) Path(self.settings["local_repo_db_resources_dir"]).mkdir( parents=True, exist_ok=True ) self._transform_setting("local_repo_db_path_db_raw", Path) Path(self.settings["local_repo_db_path_db_raw"]).mkdir( parents=True, exist_ok=True ) self._transform_setting("Dashboard_file", Path) self._transform_setting("Dashboard_pl_whitelist", Path) self._transform_setting("Dashboard_pl_whitelist_wiki", Path) self._transform_setting("Secrets_baselines_dir", Path) Path(self.settings["Secrets_baselines_dir"]).mkdir( parents=True, exist_ok=True ) self._transform_setting("CheckedInBinaries_blacklist_dir", Path) Path(self.settings["CheckedInBinaries_blacklist_dir"]).mkdir( parents=True, exist_ok=True ) self._transform_setting("SastUsageBasic_tools_dir", Path) Path(self.settings["SastUsageBasic_tools_dir"]).mkdir( parents=True, exist_ok=True ) self._transform_setting("SastUsageBasic_tool_schema", Path) self._transform_setting("SastUsageBasic_tools_csv", Path) except AttributeError as e: raise RuntimeError("Required config option was not set") from e except Exception as e: raise RuntimeError("Required config option was invalid") from e
[docs] def _transform_setting( self, setting_name: str, new_type: Type[Any] ) -> None: old_value: str = str(self.settings[setting_name]) self.settings |= {setting_name: new_type(old_value)}
context: Context = Context()