generalconfig: streamlined config discovery

determine generalconfig file location in order:
  - command line argument
  - environment variable
  - git location (../cfg)
  - local location (cwd)
  - global location (/etc/frappy)

Change-Id: Ie34bcbd5188837075ee7bb7d5029d676ae72378e
Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/34839
Reviewed-by: Bjoern Pedersen <bjoern.pedersen@frm2.tum.de>
Reviewed-by: Alexander Zaft <a.zaft@fz-juelich.de>
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
This commit is contained in:
Alexander Zaft 2024-10-18 13:23:13 +02:00 committed by Markus Zolliker
parent 261121297b
commit 632db924eb
2 changed files with 57 additions and 36 deletions

View File

@ -35,7 +35,15 @@ from frappy.server import Server
def parseArgv(argv): def parseArgv(argv):
parser = argparse.ArgumentParser(description="Manage a SECoP server") parser = argparse.ArgumentParser(
description="Manage a SECoP server",
epilog="""The server needs some configuration, by default from the
generalConfig.cfg file. the keys confdir, logdir and piddir have to
be set.
Alternatively, one can set the environment variables FRAPPY_CONFDIR
FRAPPY_LOGDIR and FRAPPY_PIDDIR to set the required values.
"""
)
loggroup = parser.add_mutually_exclusive_group() loggroup = parser.add_mutually_exclusive_group()
loggroup.add_argument("-v", "--verbose", loggroup.add_argument("-v", "--verbose",
help="Output lots of diagnostic information", help="Output lots of diagnostic information",

View File

@ -37,6 +37,10 @@ from pathlib import Path
SECoP_DEFAULT_PORT = 10767 SECoP_DEFAULT_PORT = 10767
_gcfg_help = """
"""
class GeneralConfig: class GeneralConfig:
"""generalConfig holds server configuration items """generalConfig holds server configuration items
@ -57,11 +61,12 @@ class GeneralConfig:
:param configfile: if present, keys and values from the [FRAPPY] section are read :param configfile: if present, keys and values from the [FRAPPY] section are read
default values for 'piddir', 'logdir' and 'confdir' are guessed from the The following locations are searched for the generalConfig.cfg file.
location of this source file and from sys.executable. - command line argument
- environment variable FRAPPY_CONFIG_FILE
if configfile is not given, the general config file is determined by - git location (../cfg)
the env. variable FRAPPY_CONFIG_FILE or <confdir>/generalConfig.cfg is used - local location (cwd)
- global location (/etc/frappy)
if a configfile is given, the values from the FRAPPY section are if a configfile is given, the values from the FRAPPY section are
overriding above defaults overriding above defaults
@ -69,37 +74,12 @@ class GeneralConfig:
finally, the env. variables FRAPPY_PIDDIR, FRAPPY_LOGDIR and FRAPPY_CONFDIR finally, the env. variables FRAPPY_PIDDIR, FRAPPY_LOGDIR and FRAPPY_CONFDIR
are overriding these values when given are overriding these values when given
""" """
configfile = self._get_file_location(configfile)
cfg = {} cfg = {}
mandatory = 'piddir', 'logdir', 'confdir' mandatory = 'piddir', 'logdir', 'confdir'
repodir = Path(__file__).parents[2].expanduser().resolve() repodir = Path(__file__).parents[2].expanduser().resolve()
# create default paths
if (Path(sys.executable).suffix == ".exe"
and not Path(sys.executable).name.startswith('python')):
# special MS windows environment
confdir = Path('./')
self.update_defaults(piddir=Path('./'), logdir=Path('./log'))
elif path.exists(path.join(repodir, 'cfg')):
# running from git repo
confdir = repodir / 'cfg'
# take logdir and piddir from <repodir>/cfg/generalConfig.cfg
else:
# running on installed system (typically with systemd)
self.update_defaults(
piddir=Path('/var/run/frappy'),
logdir=Path('/var/log'),
)
confdir = Path('/etc/frappy')
self.set_default('confdir', confdir)
if configfile is None:
configfile = environ.get('FRAPPY_CONFIG_FILE')
if configfile:
configfile = Path(configfile).expanduser()
if not configfile.exists():
raise FileNotFoundError(configfile)
else:
configfile = confdir / 'generalConfig.cfg'
if not configfile.exists():
configfile = None
if configfile: if configfile:
parser = ConfigParser() parser = ConfigParser()
parser.optionxform = str parser.optionxform = str
@ -113,6 +93,7 @@ class GeneralConfig:
cfg[key] = ':'.join(path.expanduser(v) for v in value.split(':')) cfg[key] = ':'.join(path.expanduser(v) for v in value.split(':'))
if cfg.get('confdir') is None: if cfg.get('confdir') is None:
cfg['confdir'] = configfile.parent cfg['confdir'] = configfile.parent
# environment variables will overwrite the config file
for key in mandatory: for key in mandatory:
env = environ.get(f'FRAPPY_{key.upper()}') env = environ.get(f'FRAPPY_{key.upper()}')
if env is not None: if env is not None:
@ -127,14 +108,46 @@ class GeneralConfig:
if missing_keys: if missing_keys:
if configfile: if configfile:
raise KeyError(f"missing value for {' and '.join(missing_keys)} in {configfile}") raise KeyError(f"missing value for {' and '.join(missing_keys)} in {configfile}")
raise KeyError('missing %s'
% ' and '.join('FRAPPY_%s' % k.upper() for k in missing_keys)) if len(missing_keys) < 3:
# user specified at least one env variable already
missing = ' (missing %s)' % ', '.join('FRAPPY_%s' % k.upper() for k in missing_keys)
else:
missing = ''
raise FileNotFoundError(
'Could not determine config file location for the general frappy config. '
f'Provide a config file or all required environment variables{missing}. '
'For more information, see frappy-server --help.'
)
if 'confdir' in cfg and isinstance(cfg['confdir'], Path): if 'confdir' in cfg and isinstance(cfg['confdir'], Path):
cfg['confdir'] = [cfg['confdir']] cfg['confdir'] = [cfg['confdir']]
# this is not customizable # this is not customizable
cfg['basedir'] = repodir cfg['basedir'] = repodir
self._config = cfg self._config = cfg
def _get_file_location(self, configfile):
"""Determining the defaultConfig.cfg location as documented in init()"""
# given as command line arg
if configfile and Path(configfile).exists():
return configfile
# if not given as argument, check different sources
# env variable
fromenv = environ.get('FRAPPY_CONFIG_FILE')
if fromenv and Path(fromenv).exists():
return fromenv
# from ../cfg (there if running from checkout)
repodir = Path(__file__).parents[2].expanduser().resolve()
if (repodir / 'cfg' / 'generalConfig.cfg').exists():
return repodir / 'cfg' / 'generalConfig.cfg'
localfile = Path.cwd() / 'generalConfig.cfg'
if localfile.exists():
return localfile
# TODO: leave this hardcoded?
globalfile = Path('/etc/frappy/generalConfig.cfg')
if globalfile.exists():
return globalfile
return None
def __getitem__(self, key): def __getitem__(self, key):
"""access for keys known to exist """access for keys known to exist