Allow defining custom variables in config file (#612).

Merge pull request #60 from diivi/feat/constants-support
This commit is contained in:
Dan Helfman 2023-03-24 22:50:57 -07:00 committed by GitHub
commit 1c51a8e229
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 56 additions and 4 deletions

View file

@ -1,4 +1,5 @@
import functools import functools
import json
import logging import logging
import os import os
@ -81,7 +82,8 @@ class Include_constructor(ruamel.yaml.SafeConstructor):
def load_configuration(filename): def load_configuration(filename):
''' '''
Load the given configuration file and return its contents as a data structure of nested dicts Load the given configuration file and return its contents as a data structure of nested dicts
and lists. and lists. Also, replace any "{constant}" strings with the value of the "constant" key in the
"constants" section of the configuration file.
Raise ruamel.yaml.error.YAMLError if something goes wrong parsing the YAML, or RecursionError Raise ruamel.yaml.error.YAMLError if something goes wrong parsing the YAML, or RecursionError
if there are too many recursive includes. if there are too many recursive includes.
@ -98,7 +100,16 @@ def load_configuration(filename):
yaml = ruamel.yaml.YAML(typ='safe') yaml = ruamel.yaml.YAML(typ='safe')
yaml.Constructor = Include_constructor_with_include_directory yaml.Constructor = Include_constructor_with_include_directory
return yaml.load(open(filename)) with open(filename) as file:
file_contents = file.read()
config = yaml.load(file_contents)
if config and 'constants' in config:
for key, value in config['constants'].items():
value = json.dumps(value)
file_contents = file_contents.replace(f'{{{key}}}', value)
config = yaml.load(file_contents)
del config['constants']
return config
DELETED_NODE = object() DELETED_NODE = object()

View file

@ -3,6 +3,17 @@ required:
- location - location
additionalProperties: false additionalProperties: false
properties: properties:
constants:
type: object
description: |
Constants to use in the configuration file. All occurences of the
constant name within culy braces will be replaced with the value.
For example, if you have a constant named "hostname" with the value
"myhostname", then the string "{hostname}" will be replaced with
"myhostname" in the configuration file.
example:
hostname: myhostname
prefix: myprefix
location: location:
type: object type: object
description: | description: |

View file

@ -10,11 +10,41 @@ from borgmatic.config import load as module
def test_load_configuration_parses_contents(): def test_load_configuration_parses_contents():
builtins = flexmock(sys.modules['builtins']) builtins = flexmock(sys.modules['builtins'])
builtins.should_receive('open').with_args('config.yaml').and_return('key: value') config_file = io.StringIO('key: value')
config_file.name = 'config.yaml'
builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
assert module.load_configuration('config.yaml') == {'key': 'value'} assert module.load_configuration('config.yaml') == {'key': 'value'}
def test_load_configuration_replaces_constants():
builtins = flexmock(sys.modules['builtins'])
config_file = io.StringIO(
'''
constants:
key: value
key: {key}
'''
)
config_file.name = 'config.yaml'
builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
assert module.load_configuration('config.yaml') == {'key': 'value'}
def test_load_configuration_replaces_complex_constants():
builtins = flexmock(sys.modules['builtins'])
config_file = io.StringIO(
'''
constants:
key:
subkey: value
key: {key}
'''
)
config_file.name = 'config.yaml'
builtins.should_receive('open').with_args('config.yaml').and_return(config_file)
assert module.load_configuration('config.yaml') == {'key': {'subkey': 'value'}}
def test_load_configuration_inlines_include_relative_to_current_directory(): def test_load_configuration_inlines_include_relative_to_current_directory():
builtins = flexmock(sys.modules['builtins']) builtins = flexmock(sys.modules['builtins'])
flexmock(module.os).should_receive('getcwd').and_return('/tmp') flexmock(module.os).should_receive('getcwd').and_return('/tmp')