From 9cc7c77ba96e8cd45c86bdf4b36d7e9bfbc366e9 Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Mon, 10 Jul 2017 10:37:11 -0700 Subject: [PATCH] Don't overwrite config files. And retain file permissions when upgrading config. --- borgmatic/commands/convert_config.py | 2 ++ borgmatic/config/generate.py | 4 ++++ borgmatic/tests/integration/config/test_generate.py | 12 ++++++++++++ 3 files changed, 18 insertions(+) diff --git a/borgmatic/commands/convert_config.py b/borgmatic/commands/convert_config.py index eb97ec4..eb0bfa8 100644 --- a/borgmatic/commands/convert_config.py +++ b/borgmatic/commands/convert_config.py @@ -78,6 +78,7 @@ def main(): # pragma: no cover args = parse_arguments(*sys.argv[1:]) schema = yaml.round_trip_load(open(validate.schema_filename()).read()) source_config = legacy.parse_configuration(args.source_config_filename, legacy.CONFIG_FORMAT) + source_config_file_mode = os.stat(args.source_config_filename).st_mode source_excludes = ( open(args.source_excludes_filename).read().splitlines() if args.source_excludes_filename @@ -87,6 +88,7 @@ def main(): # pragma: no cover destination_config = convert.convert_legacy_parsed_config(source_config, source_excludes, schema) generate.write_configuration(args.destination_config_filename, destination_config) + os.chmod(args.destination_config_filename, source_config_file_mode) # TODO: As a backstop, check that the written config can actually be read and parsed, and # that it matches the destination config data structure that was written. diff --git a/borgmatic/config/generate.py b/borgmatic/config/generate.py index 66d6c44..8b552d5 100644 --- a/borgmatic/config/generate.py +++ b/borgmatic/config/generate.py @@ -1,4 +1,5 @@ from collections import OrderedDict +import os from ruamel import yaml @@ -44,6 +45,9 @@ def write_configuration(config_filename, config): Given a target config filename and a config data structure of nested OrderedDicts, write out the config to file as YAML. ''' + if os.path.exists(config_filename): + raise FileExistsError('{} already exists. Aborting.'.format(config_filename)) + with open(config_filename, 'w') as config_file: config_file.write(yaml.round_trip_dump(config, indent=INDENT, block_seq_indent=INDENT)) diff --git a/borgmatic/tests/integration/config/test_generate.py b/borgmatic/tests/integration/config/test_generate.py index 89d8467..078367d 100644 --- a/borgmatic/tests/integration/config/test_generate.py +++ b/borgmatic/tests/integration/config/test_generate.py @@ -1,7 +1,9 @@ from io import StringIO +import os import sys from flexmock import flexmock +import pytest from borgmatic.config import generate as module @@ -15,12 +17,22 @@ def test_insert_newline_before_comment_does_not_raise(): def test_write_configuration_does_not_raise(): + flexmock(os.path).should_receive('exists').and_return(False) builtins = flexmock(sys.modules['builtins']) builtins.should_receive('open').and_return(StringIO()) module.write_configuration('config.yaml', {}) +def test_write_configuration_with_already_existing_file_raises(): + flexmock(os.path).should_receive('exists').and_return(True) + builtins = flexmock(sys.modules['builtins']) + builtins.should_receive('open').and_return(StringIO()) + + with pytest.raises(FileExistsError): + module.write_configuration('config.yaml', {}) + + def test_add_comments_to_configuration_does_not_raise(): # Ensure that it can deal with fields both in the schema and missing from the schema. config = module.yaml.comments.CommentedMap([('foo', 33), ('bar', 44), ('baz', 55)])