From 632db924ebcbd36cc26c5d0c42c6419fd41c2262 Mon Sep 17 00:00:00 2001 From: Alexander Zaft Date: Fri, 18 Oct 2024 13:23:13 +0200 Subject: [PATCH] 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 Reviewed-by: Alexander Zaft Tested-by: Jenkins Automated Tests --- bin/frappy-server | 10 ++++- frappy/lib/__init__.py | 83 ++++++++++++++++++++++++------------------ 2 files changed, 57 insertions(+), 36 deletions(-) diff --git a/bin/frappy-server b/bin/frappy-server index d5307d0..18e83a0 100755 --- a/bin/frappy-server +++ b/bin/frappy-server @@ -35,7 +35,15 @@ from frappy.server import Server 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.add_argument("-v", "--verbose", help="Output lots of diagnostic information", diff --git a/frappy/lib/__init__.py b/frappy/lib/__init__.py index 5ad0ef4..8aa2e90 100644 --- a/frappy/lib/__init__.py +++ b/frappy/lib/__init__.py @@ -37,6 +37,10 @@ from pathlib import Path SECoP_DEFAULT_PORT = 10767 +_gcfg_help = """ +""" + + class GeneralConfig: """generalConfig holds server configuration items @@ -57,11 +61,12 @@ class GeneralConfig: :param configfile: if present, keys and values from the [FRAPPY] section are read - default values for 'piddir', 'logdir' and 'confdir' are guessed from the - location of this source file and from sys.executable. - - if configfile is not given, the general config file is determined by - the env. variable FRAPPY_CONFIG_FILE or /generalConfig.cfg is used + The following locations are searched for the generalConfig.cfg file. + - command line argument + - environment variable FRAPPY_CONFIG_FILE + - git location (../cfg) + - local location (cwd) + - global location (/etc/frappy) if a configfile is given, the values from the FRAPPY section are overriding above defaults @@ -69,37 +74,12 @@ class GeneralConfig: finally, the env. variables FRAPPY_PIDDIR, FRAPPY_LOGDIR and FRAPPY_CONFDIR are overriding these values when given """ + + configfile = self._get_file_location(configfile) + cfg = {} mandatory = 'piddir', 'logdir', 'confdir' 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 /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: parser = ConfigParser() parser.optionxform = str @@ -113,6 +93,7 @@ class GeneralConfig: cfg[key] = ':'.join(path.expanduser(v) for v in value.split(':')) if cfg.get('confdir') is None: cfg['confdir'] = configfile.parent + # environment variables will overwrite the config file for key in mandatory: env = environ.get(f'FRAPPY_{key.upper()}') if env is not None: @@ -127,14 +108,46 @@ class GeneralConfig: if missing_keys: if 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): cfg['confdir'] = [cfg['confdir']] # this is not customizable cfg['basedir'] = repodir 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): """access for keys known to exist