From 61f88228b0c479cb9f80bcf12b2642bc6813d6cb Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Mon, 10 Jul 2017 15:20:50 -0700 Subject: [PATCH] When writing config, make containing directory if necessary. Also default to tighter permissions. --- borgmatic/commands/borgmatic.py | 2 +- borgmatic/commands/convert_config.py | 7 +++++-- borgmatic/config/generate.py | 11 +++++++++-- .../tests/integration/config/test_generate.py | 14 ++++++++++++-- 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/borgmatic/commands/borgmatic.py b/borgmatic/commands/borgmatic.py index 1fcc582..a1d53e3 100644 --- a/borgmatic/commands/borgmatic.py +++ b/borgmatic/commands/borgmatic.py @@ -42,7 +42,7 @@ def parse_arguments(*arguments): def main(): # pragma: no cover try: # TODO: Detect whether only legacy config is present. If so, inform the user about how to - # upgrade, then exet. + # upgrade, then exit. args = parse_arguments(*sys.argv[1:]) config = parse_configuration(args.config_filename, schema_filename()) diff --git a/borgmatic/commands/convert_config.py b/borgmatic/commands/convert_config.py index eb0bfa8..5e61a77 100644 --- a/borgmatic/commands/convert_config.py +++ b/borgmatic/commands/convert_config.py @@ -87,8 +87,11 @@ 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) + generate.write_configuration( + args.destination_config_filename, + destination_config, + mode=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 8b552d5..626747f 100644 --- a/borgmatic/config/generate.py +++ b/borgmatic/config/generate.py @@ -40,17 +40,24 @@ def _schema_to_sample_configuration(schema, level=0): return config -def write_configuration(config_filename, config): +def write_configuration(config_filename, config, mode=0o600): ''' Given a target config filename and a config data structure of nested OrderedDicts, write out the - config to file as YAML. + config to file as YAML. Create any containing directories as needed. ''' if os.path.exists(config_filename): raise FileExistsError('{} already exists. Aborting.'.format(config_filename)) + try: + os.makedirs(os.path.dirname(config_filename), mode=0o700) + except FileExistsError: + pass + with open(config_filename, 'w') as config_file: config_file.write(yaml.round_trip_dump(config, indent=INDENT, block_seq_indent=INDENT)) + os.chmod(config_filename, mode) + def add_comments_to_configuration(config, schema, indent=0): ''' diff --git a/borgmatic/tests/integration/config/test_generate.py b/borgmatic/tests/integration/config/test_generate.py index 078367d..048315a 100644 --- a/borgmatic/tests/integration/config/test_generate.py +++ b/borgmatic/tests/integration/config/test_generate.py @@ -18,21 +18,31 @@ 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) + flexmock(os).should_receive('makedirs') builtins = flexmock(sys.modules['builtins']) builtins.should_receive('open').and_return(StringIO()) + flexmock(os).should_receive('chmod') 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_write_configuration_with_already_existing_directory_does_not_raise(): + flexmock(os.path).should_receive('exists').and_return(False) + flexmock(os).should_receive('makedirs').and_raise(FileExistsError) + builtins = flexmock(sys.modules['builtins']) + builtins.should_receive('open').and_return(StringIO()) + flexmock(os).should_receive('chmod') + + 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)])