Skip to content

Commit 3cb6078

Browse files
committed
Merge branch 'lazyconfig' into 'master'
Lazyload environment variables in config.yml See merge request fdroid/fdroidserver!1645
2 parents e44fd22 + cd1630d commit 3cb6078

2 files changed

Lines changed: 101 additions & 37 deletions

File tree

fdroidserver/common.py

Lines changed: 56 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,60 @@ def config_type_check(path, data):
546546
)
547547

548548

549+
class _Config(dict):
550+
def __init__(self, default={}):
551+
super(_Config, self).__init__(default)
552+
self.loaded = {}
553+
554+
def lazyget(self, key):
555+
if key not in self.loaded:
556+
value = super(_Config, self).__getitem__(key)
557+
558+
if key == 'serverwebroot':
559+
roots = parse_list_of_dicts(value)
560+
rootlist = []
561+
for d in roots:
562+
# since this is used with rsync, where trailing slashes have
563+
# meaning, ensure there is always a trailing slash
564+
rootstr = d.get('url')
565+
if not rootstr:
566+
logging.error('serverwebroot: has blank value!')
567+
continue
568+
if rootstr[-1] != '/':
569+
rootstr += '/'
570+
d['url'] = rootstr.replace('//', '/')
571+
rootlist.append(d)
572+
self.loaded[key] = rootlist
573+
574+
elif key == 'servergitmirrors':
575+
self.loaded[key] = parse_list_of_dicts(value)
576+
577+
elif isinstance(value, dict) and 'env' in value and len(value) == 1:
578+
var = value['env']
579+
if var in os.environ:
580+
self.loaded[key] = os.getenv(var)
581+
else:
582+
logging.error(
583+
_(
584+
'Environment variable {var} from {configname} is not set!'
585+
).format(var=value['env'], configname=key)
586+
)
587+
self.loaded[key] = None
588+
else:
589+
self.loaded[key] = value
590+
591+
return self.loaded[key]
592+
593+
def __getitem__(self, key):
594+
return self.lazyget(key)
595+
596+
def get(self, key, default=None, /):
597+
try:
598+
return self.lazyget(key)
599+
except KeyError:
600+
return default
601+
602+
549603
def read_config():
550604
"""Read the repository config.
551605
@@ -601,25 +655,7 @@ def read_config():
601655

602656
fill_config_defaults(config)
603657

604-
if 'serverwebroot' in config:
605-
roots = parse_list_of_dicts(config['serverwebroot'])
606-
rootlist = []
607-
for d in roots:
608-
# since this is used with rsync, where trailing slashes have
609-
# meaning, ensure there is always a trailing slash
610-
rootstr = d.get('url')
611-
if not rootstr:
612-
logging.error('serverwebroot: has blank value!')
613-
continue
614-
if rootstr[-1] != '/':
615-
rootstr += '/'
616-
d['url'] = rootstr.replace('//', '/')
617-
rootlist.append(d)
618-
config['serverwebroot'] = rootlist
619-
620658
if 'servergitmirrors' in config:
621-
config['servergitmirrors'] = parse_list_of_dicts(config['servergitmirrors'])
622-
623659
limit = config['git_mirror_size_limit']
624660
config['git_mirror_size_limit'] = parse_human_readable_size(limit)
625661

@@ -642,15 +678,7 @@ def read_config():
642678
continue
643679
elif isinstance(dictvalue, dict):
644680
for k, v in dictvalue.items():
645-
if k == 'env':
646-
env = os.getenv(v)
647-
if env:
648-
config[configname] = env
649-
else:
650-
confignames_to_delete.add(configname)
651-
logging.error(_('Environment variable {var} from {configname} is not set!')
652-
.format(var=k, configname=configname))
653-
else:
681+
if k != 'env':
654682
confignames_to_delete.add(configname)
655683
logging.error(_('Unknown entry {key} in {configname}')
656684
.format(key=k, configname=configname))
@@ -670,6 +698,7 @@ def read_config():
670698
)
671699
)
672700

701+
config = _Config(config)
673702
return config
674703

675704

tests/test_common.py

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2029,6 +2029,49 @@ def test_config_dict_with_int_keys(self):
20292029
config = fdroidserver.common.read_config()
20302030
self.assertEqual('/usr/lib/jvm/java-8-openjdk', config['java_paths']['8'])
20312031

2032+
@mock.patch.dict(os.environ, {'PATH': os.getenv('PATH')}, clear=True)
2033+
def test_config_lazy_load_env_vars(self):
2034+
"""Test the environment variables in config.yml is lazy loaded.
2035+
2036+
It shouldn't throw errors when read the config if the environment variables are
2037+
not set. It should throw errors when the variables are get from the config.
2038+
"""
2039+
os.chdir(self.testdir)
2040+
fdroidserver.common.write_config_file(
2041+
textwrap.dedent(
2042+
"""
2043+
serverwebroot: {env: serverwebroot}
2044+
servergitmirrors:
2045+
- url: {env: mirror1}
2046+
- url: {env: mirror2}
2047+
keypass: {env: keypass}
2048+
keystorepass: {env: keystorepass}
2049+
"""
2050+
)
2051+
)
2052+
with self.assertNoLogs(level=logging.ERROR):
2053+
config = fdroidserver.common.read_config()
2054+
2055+
# KeyError should be raised if a key is not in the config.yml
2056+
with self.assertRaises(KeyError):
2057+
config['gpghome']
2058+
2059+
self.assertEqual(config.get('gpghome', 'gpg'), 'gpg')
2060+
os.environ.update({key: f"{key}supersecret" for key in ["serverwebroot", "mirror1", "mirror2", "keystorepass"]})
2061+
self.assertEqual(config['keystorepass'], 'keystorepasssupersecret')
2062+
self.assertEqual(config['serverwebroot'], [{'url': 'serverwebrootsupersecret/'}])
2063+
self.assertEqual(config['servergitmirrors'], [{'url': 'mirror1supersecret'}, {'url': 'mirror2supersecret'}])
2064+
2065+
@mock.patch.dict(os.environ, {'PATH': os.getenv('PATH')}, clear=True)
2066+
def test_config_lazy_load_env_vars_not_set(self):
2067+
os.chdir(self.testdir)
2068+
fdroidserver.common.write_config_file('keypass: {env: keypass}')
2069+
fdroidserver.common.read_config()
2070+
with self.assertLogs(level=logging.ERROR) as lw:
2071+
fdroidserver.common.config['keypass']
2072+
self.assertTrue('is not set' in lw.output[0])
2073+
self.assertEqual(1, len(lw.output))
2074+
20322075
@mock.patch.dict(os.environ, {'PATH': os.getenv('PATH')}, clear=True)
20332076
def test_test_sdk_exists_fails_on_bad_sdk_path(self):
20342077
config = {'sdk_path': 'nothinghere'}
@@ -3465,7 +3508,7 @@ def test_get_config(self):
34653508
self.assertIsNone(fdroidserver.common.config)
34663509
config = fdroidserver.common.read_config()
34673510
self.assertIsNotNone(fdroidserver.common.config)
3468-
self.assertEqual(dict, type(config))
3511+
self.assertTrue(isinstance(config, dict))
34693512
self.assertEqual(config, fdroidserver.common.config)
34703513

34713514
def test_get_config_global(self):
@@ -3475,7 +3518,7 @@ def test_get_config_global(self):
34753518
self.assertIsNone(fdroidserver.common.config)
34763519
c = fdroidserver.common.read_config()
34773520
self.assertIsNotNone(fdroidserver.common.config)
3478-
self.assertEqual(dict, type(c))
3521+
self.assertTrue(isinstance(c, dict))
34793522
self.assertEqual(c, fdroidserver.common.config)
34803523
self.assertTrue(
34813524
'config' not in vars() and 'config' not in globals(),
@@ -3515,14 +3558,6 @@ def test_config_perm_env_warning(self):
35153558
self.assertTrue('unsafe' in lw.output[0])
35163559
self.assertEqual(1, len(lw.output))
35173560

3518-
@mock.patch.dict(os.environ, {'PATH': os.getenv('PATH')}, clear=True)
3519-
def test_config_perm_unset_env_no_warning(self):
3520-
fdroidserver.common.write_config_file('keypass: {env: keypass}')
3521-
with self.assertLogs(level=logging.WARNING) as lw:
3522-
fdroidserver.common.read_config()
3523-
self.assertTrue('unsafe' not in lw.output[0])
3524-
self.assertEqual(1, len(lw.output))
3525-
35263561

35273562
class GetHeadCommitIdTest(unittest.TestCase):
35283563
"""Test and compare two methods of getting the commit ID."""

0 commit comments

Comments
 (0)