From 6f300b00795dd76ced62407f9eb065778a8bcbe6 Mon Sep 17 00:00:00 2001 From: Divyansh Singh Date: Fri, 24 Mar 2023 02:39:37 +0530 Subject: [PATCH 1/3] feat: constants support --- borgmatic/config/load.py | 13 +++++++++++-- borgmatic/config/schema.yaml | 11 +++++++++++ tests/integration/config/test_load.py | 17 ++++++++++++++++- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/borgmatic/config/load.py b/borgmatic/config/load.py index 04461af..e379537 100644 --- a/borgmatic/config/load.py +++ b/borgmatic/config/load.py @@ -81,7 +81,8 @@ class Include_constructor(ruamel.yaml.SafeConstructor): def load_configuration(filename): ''' 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 if there are too many recursive includes. @@ -98,7 +99,15 @@ def load_configuration(filename): yaml = ruamel.yaml.YAML(typ='safe') yaml.Constructor = Include_constructor_with_include_directory - return yaml.load(open(filename)) + with open(filename) as f: + file_contents = f.read() + config = yaml.load(file_contents) + if config and 'constants' in config: + for key, value in config['constants'].items(): + file_contents = file_contents.replace(f'{{{key}}}', str(value)) + config = yaml.load(file_contents) + del config['constants'] + return config DELETED_NODE = object() diff --git a/borgmatic/config/schema.yaml b/borgmatic/config/schema.yaml index d4d57ab..c01f3db 100644 --- a/borgmatic/config/schema.yaml +++ b/borgmatic/config/schema.yaml @@ -3,6 +3,17 @@ required: - location additionalProperties: false 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: type: object description: | diff --git a/tests/integration/config/test_load.py b/tests/integration/config/test_load.py index e1ecc8a..5b8ee9b 100644 --- a/tests/integration/config/test_load.py +++ b/tests/integration/config/test_load.py @@ -10,8 +10,23 @@ from borgmatic.config import load as module def test_load_configuration_parses_contents(): 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'} + +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'} From af95134cd2ec7a779600247be818bf180aa8de7b Mon Sep 17 00:00:00 2001 From: Divyansh Singh Date: Sat, 25 Mar 2023 02:03:36 +0530 Subject: [PATCH 2/3] add test for complex constant --- borgmatic/config/load.py | 4 +++- tests/integration/config/test_load.py | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/borgmatic/config/load.py b/borgmatic/config/load.py index e379537..3218112 100644 --- a/borgmatic/config/load.py +++ b/borgmatic/config/load.py @@ -1,4 +1,5 @@ import functools +import json import logging import os @@ -104,7 +105,8 @@ def load_configuration(filename): config = yaml.load(file_contents) if config and 'constants' in config: for key, value in config['constants'].items(): - file_contents = file_contents.replace(f'{{{key}}}', str(value)) + value = json.dumps(value) + file_contents = file_contents.replace(f'{{{key}}}', value) config = yaml.load(file_contents) del config['constants'] return config diff --git a/tests/integration/config/test_load.py b/tests/integration/config/test_load.py index 5b8ee9b..3382d73 100644 --- a/tests/integration/config/test_load.py +++ b/tests/integration/config/test_load.py @@ -30,6 +30,21 @@ def test_load_configuration_replaces_constants(): 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(): builtins = flexmock(sys.modules['builtins']) flexmock(module.os).should_receive('getcwd').and_return('/tmp') From 78e8bb6c8c0d47d491f7a9a9cb1a4d892ef53963 Mon Sep 17 00:00:00 2001 From: Divyansh Singh Date: Sat, 25 Mar 2023 02:08:52 +0530 Subject: [PATCH 3/3] reformat --- borgmatic/config/load.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/borgmatic/config/load.py b/borgmatic/config/load.py index 3218112..01ceab2 100644 --- a/borgmatic/config/load.py +++ b/borgmatic/config/load.py @@ -100,8 +100,8 @@ def load_configuration(filename): yaml = ruamel.yaml.YAML(typ='safe') yaml.Constructor = Include_constructor_with_include_directory - with open(filename) as f: - file_contents = f.read() + 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():