implement config layering

This commit is contained in:
Jakob Ketterl
2021-02-11 19:31:44 +01:00
parent e926611307
commit f23fa59ac3
20 changed files with 524 additions and 147 deletions

View File

@@ -1,129 +1,32 @@
from configparser import ConfigParser
from owrx.property import PropertyLayer
import importlib.util
import os
import json
from glob import glob
from owrx.config.error import ConfigError, ConfigNotFoundException
from owrx.config.migration import ConfigMigratorVersion1, ConfigMigratorVersion2
import logging
logger = logging.getLogger(__name__)
from owrx.property import PropertyStack
from owrx.config.error import ConfigError
from owrx.config.defaults import defaultConfig
from owrx.config.dynamic import DynamicConfig
from owrx.config.classic import ClassicConfig
class CoreConfig(object):
defaults = {
"core": {
"data_directory": "/var/lib/openwebrx",
"temporary_directory": "/tmp",
},
"web": {
"port": 8073,
},
"aprs": {
"symbols_path": "/usr/share/aprs-symbols/png"
}
}
class Config(PropertyStack):
sharedConfig = None
def __init__(self):
config = ConfigParser()
# set up config defaults
config.read_dict(CoreConfig.defaults)
# check for overrides
overrides_dir = "/etc/openwebrx/openwebrx.conf.d"
if os.path.exists(overrides_dir) and os.path.isdir(overrides_dir):
overrides = glob(overrides_dir + "/*.conf")
else:
overrides = []
# sequence things together
config.read(["./openwebrx.conf", "/etc/openwebrx/openwebrx.conf"] + overrides)
self.data_directory = config.get("core", "data_directory")
CoreConfig.checkDirectory(self.data_directory, "data_directory")
self.temporary_directory = config.get("core", "temporary_directory")
CoreConfig.checkDirectory(self.temporary_directory, "temporary_directory")
self.web_port = config.getint("web", "port")
self.aprs_symbols_path = config.get("aprs", "symbols_path")
@staticmethod
def checkDirectory(dir, key):
if not os.path.exists(dir):
raise ConfigError(key, "{dir} doesn't exist".format(dir=dir))
if not os.path.isdir(dir):
raise ConfigError(key, "{dir} is not a directory".format(dir=dir))
if not os.access(dir, os.W_OK):
raise ConfigError(key, "{dir} is not writable".format(dir=dir))
def get_web_port(self):
return self.web_port
def get_data_directory(self):
return self.data_directory
def get_temporary_directory(self):
return self.temporary_directory
def get_aprs_symbols_path(self):
return self.aprs_symbols_path
class Config:
sharedConfig = None
currentVersion = 3
migrators = {
1: ConfigMigratorVersion1(),
2: ConfigMigratorVersion2(),
}
@staticmethod
def _loadPythonFile(file):
spec = importlib.util.spec_from_file_location("config_webrx", file)
cfg = importlib.util.module_from_spec(spec)
spec.loader.exec_module(cfg)
pm = PropertyLayer()
for name, value in cfg.__dict__.items():
if name.startswith("__"):
continue
pm[name] = value
return pm
@staticmethod
def _loadJsonFile(file):
with open(file, "r") as f:
pm = PropertyLayer()
for k, v in json.load(f).items():
pm[k] = v
return pm
@staticmethod
def _getSettingsFile():
coreConfig = CoreConfig()
return "{data_directory}/settings.json".format(data_directory=coreConfig.get_data_directory())
@staticmethod
def _loadConfig():
for file in [Config._getSettingsFile(), "/etc/openwebrx/config_webrx.py", "./config_webrx.py"]:
try:
if file.endswith(".py"):
return Config._loadPythonFile(file)
elif file.endswith(".json"):
return Config._loadJsonFile(file)
else:
logger.warning("unsupported file type: %s", file)
except FileNotFoundError:
pass
raise ConfigNotFoundException("no usable config found! please make sure you have a valid configuration file!")
super().__init__()
self.storableConfig = DynamicConfig()
layers = [
self.storableConfig,
ClassicConfig(),
defaultConfig,
]
for i, l in enumerate(layers):
self.addLayer(i, l)
@staticmethod
def get():
if Config.sharedConfig is None:
Config.sharedConfig = Config._migrate(Config._loadConfig())
Config.sharedConfig = Config()
return Config.sharedConfig
@staticmethod
def store():
with open(Config._getSettingsFile(), "w") as file:
json.dump(Config.get().__dict__(), file, indent=4)
def store(self):
self.storableConfig.store()
@staticmethod
def validateConfig():
@@ -131,14 +34,6 @@ class Config:
# just basic loading verification
Config.get()
@staticmethod
def _migrate(config):
version = config["version"] if "version" in config else 1
if version == Config.currentVersion:
return config
logger.debug("migrating config from version %i", version)
migrators = [Config.migrators[i] for i in range(version, Config.currentVersion)]
for migrator in migrators:
config = migrator.migrate(config)
return config
def __setitem__(self, key, value):
# in the config, all writes go to the json layer
return self.storableConfig.__setitem__(key, value)