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()