瀏覽代碼

Add "generate-borgmatic-config --overwrite" flag to replace an existing destination file (#539).

Dan Helfman 3 年之前
父節點
當前提交
2bc91ac3d2

+ 1 - 0
NEWS

@@ -6,6 +6,7 @@
  * #536: Fix generate-borgmatic-config to support more complex schema changes like the new
    Healthchecks configuration options when the "--source" flag is used.
  * #538: Add support for "borgmatic borg debug" command.
+ * #539: Add "generate-borgmatic-config --overwrite" flag to replace an existing destination file.
  * Add Bash completion script so you can tab-complete the borgmatic command-line. See the
    documentation for more information:
    https://torsion.org/borgmatic/docs/how-to/set-up-backups/#shell-completion

+ 11 - 2
borgmatic/commands/generate_config.py

@@ -23,10 +23,16 @@ def parse_arguments(*arguments):
         '--destination',
         dest='destination_filename',
         default=DEFAULT_DESTINATION_CONFIG_FILENAME,
-        help='Destination YAML configuration file. Default: {}'.format(
+        help='Destination YAML configuration file, default: {}'.format(
             DEFAULT_DESTINATION_CONFIG_FILENAME
         ),
     )
+    parser.add_argument(
+        '--overwrite',
+        default=False,
+        action='store_true',
+        help='Whether to overwrite any existing destination file, defaults to false',
+    )
 
     return parser.parse_args(arguments)
 
@@ -36,7 +42,10 @@ def main():  # pragma: no cover
         args = parse_arguments(*sys.argv[1:])
 
         generate.generate_sample_configuration(
-            args.source_filename, args.destination_filename, validate.schema_filename()
+            args.source_filename,
+            args.destination_filename,
+            validate.schema_filename(),
+            overwrite=args.overwrite,
         )
 
         print('Generated a sample configuration file at {}.'.format(args.destination_filename))

+ 17 - 8
borgmatic/config/generate.py

@@ -109,13 +109,18 @@ def render_configuration(config):
     return rendered.getvalue()
 
 
-def write_configuration(config_filename, rendered_config, mode=0o600):
+def write_configuration(config_filename, rendered_config, mode=0o600, overwrite=False):
     '''
     Given a target config filename and rendered config YAML, write it out to file. Create any
-    containing directories as needed.
+    containing directories as needed. But if the file already exists and overwrite is False,
+    abort before writing anything.
     '''
-    if os.path.exists(config_filename):
-        raise FileExistsError('{} already exists. Aborting.'.format(config_filename))
+    if not overwrite and os.path.exists(config_filename):
+        raise FileExistsError(
+            '{} already exists. Aborting. Use --overwrite to replace the file.'.format(
+                config_filename
+            )
+        )
 
     try:
         os.makedirs(os.path.dirname(config_filename), mode=0o700)
@@ -263,12 +268,15 @@ def merge_source_configuration_into_destination(destination_config, source_confi
     return destination_config
 
 
-def generate_sample_configuration(source_filename, destination_filename, schema_filename):
+def generate_sample_configuration(
+    source_filename, destination_filename, schema_filename, overwrite=False
+):
     '''
     Given an optional source configuration filename, and a required destination configuration
-    filename, and the path to a schema filename in a YAML rendition of the JSON Schema format,
-    write out a sample configuration file based on that schema. If a source filename is provided,
-    merge the parsed contents of that configuration into the generated configuration.
+    filename, the path to a schema filename in a YAML rendition of the JSON Schema format, and
+    whether to overwrite a destination file, write out a sample configuration file based on that
+    schema. If a source filename is provided, merge the parsed contents of that configuration into
+    the generated configuration.
     '''
     schema = yaml.round_trip_load(open(schema_filename))
     source_config = None
@@ -284,4 +292,5 @@ def generate_sample_configuration(source_filename, destination_filename, schema_
     write_configuration(
         destination_filename,
         _comment_out_optional_configuration(render_configuration(destination_config)),
+        overwrite=overwrite,
     )

+ 14 - 2
tests/integration/commands/test_generate_config.py

@@ -1,13 +1,25 @@
 from borgmatic.commands import generate_config as module
 
 
-def test_parse_arguments_with_no_arguments_uses_defaults():
+def test_parse_arguments_with_no_arguments_uses_default_destination():
     parser = module.parse_arguments()
 
     assert parser.destination_filename == module.DEFAULT_DESTINATION_CONFIG_FILENAME
 
 
-def test_parse_arguments_with_filename_argument_overrides_defaults():
+def test_parse_arguments_with_destination_argument_overrides_default():
     parser = module.parse_arguments('--destination', 'config.yaml')
 
     assert parser.destination_filename == 'config.yaml'
+
+
+def test_parse_arguments_parses_source():
+    parser = module.parse_arguments('--source', 'source.yaml', '--destination', 'config.yaml')
+
+    assert parser.source_filename == 'source.yaml'
+
+
+def test_parse_arguments_parses_overwrite():
+    parser = module.parse_arguments('--destination', 'config.yaml', '--overwrite')
+
+    assert parser.overwrite

+ 7 - 1
tests/integration/config/test_generate.py

@@ -87,7 +87,7 @@ location:
     assert module._comment_out_optional_configuration(config.strip()) == expected_config.strip()
 
 
-def testrender_configuration_converts_configuration_to_yaml_string():
+def test_render_configuration_converts_configuration_to_yaml_string():
     yaml_string = module.render_configuration({'foo': 'bar'})
 
     assert yaml_string == 'foo: bar\n'
@@ -110,6 +110,12 @@ def test_write_configuration_with_already_existing_file_raises():
         module.write_configuration('config.yaml', 'config: yaml')
 
 
+def test_write_configuration_with_already_existing_file_and_overwrite_does_not_raise():
+    flexmock(os.path).should_receive('exists').and_return(True)
+
+    module.write_configuration('config.yaml', 'config: yaml', overwrite=True)
+
+
 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)