expand {env: foo} in any place a string can be

`keypass: {env: keypass}` has been in use in production repos for
years.  That is not anything new. It makes it possible to maintain
_config.yml_ publicly even when it needs secrets.  This change makes
sure it is possible to use {env: foo} syntax anywhere where a string
value is valid. The "list of dicts" values can be str, list of str or
list of dicts with str.

Before the {env: keypass} syntax, the actual password was just inline
in the config file.  Before this commit, it was only possible to use
{env: key} syntax in simple, string-only configs, e.g. from
examples/config.yml:
This commit is contained in:
Hans-Christoph Steiner 2025-02-27 15:48:58 +01:00
parent 031ae1103e
commit 081e02c109
3 changed files with 116 additions and 7 deletions

View file

@ -642,16 +642,43 @@ def read_config():
return config
def expand_env_dict(s):
"""Expand env var dict to a string value.
{env: varName} syntax can be used to replace any string value in the
config with the value of an environment variable "varName". This
allows for secrets management when commiting the config file to a
public git repo.
"""
if not s or type(s) not in (str, dict):
return
if isinstance(s, dict):
if 'env' not in s or len(s) > 1:
raise TypeError(_('Only accepts a single key "env"'))
var = s['env']
s = os.getenv(var)
if not s:
logging.error(
_('Environment variable {{env: {var}}} is not set!').format(var=var)
)
return
return os.path.expanduser(s)
def parse_mirrors_config(mirrors):
"""Mirrors can be specified as a string, list of strings, or dictionary map."""
if isinstance(mirrors, str):
return [{"url": mirrors}]
elif all(isinstance(item, str) for item in mirrors):
return [{'url': i} for i in mirrors]
elif all(isinstance(item, dict) for item in mirrors):
return [{"url": expand_env_dict(mirrors)}]
if isinstance(mirrors, dict):
return [{"url": expand_env_dict(mirrors)}]
if all(isinstance(item, str) for item in mirrors):
return [{'url': expand_env_dict(i)} for i in mirrors]
if all(isinstance(item, dict) for item in mirrors):
for item in mirrors:
item['url'] = expand_env_dict(item['url'])
return mirrors
else:
raise TypeError(_('only accepts strings, lists, and tuples'))
raise TypeError(_('only accepts strings, lists, and tuples'))
def get_mirrors(url, filename=None):