瀏覽代碼

When writing config, make containing directory if necessary. Also default to tighter permissions.

Dan Helfman 8 年之前
父節點
當前提交
61f88228b0

+ 1 - 1
borgmatic/commands/borgmatic.py

@@ -42,7 +42,7 @@ def parse_arguments(*arguments):
 def main():  # pragma: no cover
 def main():  # pragma: no cover
     try:
     try:
         # TODO: Detect whether only legacy config is present. If so, inform the user about how to
         # 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:])
         args = parse_arguments(*sys.argv[1:])
         config = parse_configuration(args.config_filename, schema_filename())
         config = parse_configuration(args.config_filename, schema_filename())

+ 5 - 2
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)
         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
         # 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.
         # that it matches the destination config data structure that was written.

+ 9 - 2
borgmatic/config/generate.py

@@ -40,17 +40,24 @@ def _schema_to_sample_configuration(schema, level=0):
     return config
     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
     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):
     if os.path.exists(config_filename):
         raise FileExistsError('{} already exists. Aborting.'.format(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:
     with open(config_filename, 'w') as config_file:
         config_file.write(yaml.round_trip_dump(config, indent=INDENT, block_seq_indent=INDENT))
         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):
 def add_comments_to_configuration(config, schema, indent=0):
     '''
     '''

+ 12 - 2
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():
 def test_write_configuration_does_not_raise():
     flexmock(os.path).should_receive('exists').and_return(False)
     flexmock(os.path).should_receive('exists').and_return(False)
+    flexmock(os).should_receive('makedirs')
     builtins = flexmock(sys.modules['builtins'])
     builtins = flexmock(sys.modules['builtins'])
     builtins.should_receive('open').and_return(StringIO())
     builtins.should_receive('open').and_return(StringIO())
+    flexmock(os).should_receive('chmod')
 
 
     module.write_configuration('config.yaml', {})
     module.write_configuration('config.yaml', {})
 
 
 
 
 def test_write_configuration_with_already_existing_file_raises():
 def test_write_configuration_with_already_existing_file_raises():
     flexmock(os.path).should_receive('exists').and_return(True)
     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):
     with pytest.raises(FileExistsError):
         module.write_configuration('config.yaml', {})
         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():
 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.
     # 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)])
     config = module.yaml.comments.CommentedMap([('foo', 33), ('bar', 44), ('baz', 55)])