Improve error on empty or non dict config file (#3238)

Test if config (file) parsed with yaml_load() contains a valid Mapping
object, otherwise Patroni throws an explicit exception. It also makes
the Patroni output more explicit when using that kind of "invalid"
configuration.

``` console
$ touch /tmp/patroni.yaml
$ patroni --validate-config /tmp/patroni.yaml
/tmp/patroni.yaml does not contain a dict
invalid config file /tmp/patroni.yaml
```
reportUnnecessaryIsInstance is explicitly ignored since we can't
determine what yaml_safeload can bring from a YAML config (list,
dict,...).
This commit is contained in:
Julian
2025-01-17 14:44:47 +01:00
committed by GitHub
parent 836e527e6d
commit 26ae38960a
2 changed files with 34 additions and 1 deletions

View File

@@ -188,6 +188,7 @@ class Config(object):
:raises:
:class:`ConfigParseError`: if *path* is invalid.
:class:`ConfigParseError`: if *path* does not contain dict (empty file or no mapping values).
"""
if os.path.isfile(path):
files = [path]
@@ -202,7 +203,10 @@ class Config(object):
for fname in files:
with open(fname) as f:
config = yaml.safe_load(f)
patch_config(overall_config, config)
if not isinstance(config, dict):
logger.error('%s does not contain a dict', fname)
raise ConfigParseError(f'invalid config file {fname}')
patch_config(overall_config, cast(Dict[Any, Any], config))
return overall_config
def _load_config_file(self) -> Dict[str, Any]:

View File

@@ -158,6 +158,35 @@ class TestConfig(unittest.TestCase):
def test_invalid_path(self):
self.assertRaises(ConfigParseError, Config, 'postgres0')
@patch('os.path.exists', Mock(return_value=True))
@patch('os.path.isfile', Mock(side_effect=lambda fname: fname != 'postgres0'))
@patch('os.path.isdir', Mock(return_value=True))
@patch('os.listdir', Mock(return_value=['00-empty.yml', '00-base.yml']))
@patch('patroni.config.logger')
def test_invalid_empty_config_file(self, mock_logger):
def open_mock(fname, *args, **kwargs):
if fname.endswith('00-base.yml'):
return io.StringIO(
u'''
test: True
test2:
child-1: somestring
child-2: 5
child-3: False
test3: True
test4:
- abc: 3
- abc: 4
''')
elif fname.endswith('00-empty.yml'):
return io.StringIO(u'''---''')
with patch('builtins.open', MagicMock(side_effect=open_mock)):
self.assertRaises(ConfigParseError, Config, 'postgres0')
mock_logger.error.assert_called_once_with(
'%s does not contain a dict',
'postgres0\\00-empty.yml' if sys.platform == 'win32' else 'postgres0/00-empty.yml')
@patch.object(Config, 'get')
@patch('patroni.config.logger')
def test__validate_tags(self, mock_logger, mock_get):