phi/src/phi/config.py

145 lines
4.1 KiB
Python

import os.path
import pkg_resources
import yaml
NAME = "phi"
DEFAULT_CONFIG = {
"core": {"listen": {"host": "localhost", "port": 8080}, "cookiestore_secret": None},
"ldap": {
"host": "localhost",
"port": 389,
"encryption": "TLSv1.2",
"ciphers": "HIGH",
"validate": True,
"ca_certs": pkg_resources.resource_filename(NAME, "openldap/cert.pem"),
"base_dn": None,
"attribute_mail": "mail",
},
"logging": {
"version": 1,
"formatters": {"default": {"format": "[%(name)s %(levelname)s] %(message)s"}},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "default",
"stream": "ext://sys.stdout",
},
"file": {
"class": "logging.FileHandler",
"formatter": "default",
"filename": "phi.log",
},
},
"loggers": {
"phi": {
"level": "INFO",
"handlers": ["console", "file"],
},
"aiohttp": {
"level": "INFO",
"handlers": ["console", "file"],
},
"bonsai": {
"level": "WARNING",
"handlers": ["console", "file"],
},
},
},
}
DUMMY_CONFIG = {"core": {}, "ldap": {}, "logging": {}}
CONFIG_FILE = "config.yml"
CONFIG_PATHS = [
"./",
"~/.config/" + NAME + "/",
"/usr/local/etc/" + NAME + "/",
"/etc/" + NAME + "/",
]
CONFIG_FILES = [os.path.join(p, CONFIG_FILE) for p in CONFIG_PATHS]
def get_config(config_path=None):
"""Return the path of the found configuration file and its content
:param config_path: optional path to a config file.
:returns: (path, config)
:rtype: (str, dict)
"""
if config_path:
with open(config_path) as c:
config = yaml.safe_load(c)
return config_path, config
for f in CONFIG_FILES:
try:
with open(f, "r") as c:
config = yaml.safe_load(c)
return (f, config)
except FileNotFoundError:
# Skip to the next file.
# We only care if the file is preset but it's not
# accessible or if the file is not present at all
# in any of CONFIG_PATHS.
pass
return None, DUMMY_CONFIG
def merge_config(cli_config, file_config):
"""
Merge the cli-provided and file-provided config.
"""
return recursive_merge(cli_config, file_config)
def _init_with_shape_of(element):
if isinstance(element, dict):
return {}
elif isinstance(element, list):
return []
return None
def recursive_merge(main_config, aux_config):
def _recursive_merge(main, aux, default, key, _ret_config):
if isinstance(default, dict):
_sub_conf = {}
for k, v in default.items():
_main = main[k] if k in main else _init_with_shape_of(v)
_aux = aux[k] if k in aux else _init_with_shape_of(v)
_recursive_merge(_main, _aux, v, k, _sub_conf)
_ret_config[key] = _sub_conf
elif isinstance(default, list):
_main = main.copy()
if aux is not None:
_main.extend(aux)
_ret_config[key] = list(set(_main))
elif isinstance(default, bool):
_ret_config[key] = default and aux and main
else:
if main is not None:
_ret_config[key] = main
elif aux is not None:
_ret_config[key] = aux
else:
_ret_config[key] = default
_config = {}
_recursive_merge(main_config, aux_config, DEFAULT_CONFIG, "ROOT", _config)
return _config["ROOT"]
def extract_secret(config):
try:
secret = config["core"]["cookiestore_secret"].encode("utf-8")
if len(secret) != 32:
raise ValueError(
"The provided core.cookiestore_secret must be 32 bytes long"
)
return secret
except KeyError:
raise RuntimeError("You must provide a core.cookiestore_secret")