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

This commit is contained in:
Dan Helfman 2022-05-29 16:03:55 -07:00
parent 5b615d51a4
commit 2bc91ac3d2
5 changed files with 50 additions and 13 deletions

1
NEWS
View file

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

View file

@ -23,10 +23,16 @@ def parse_arguments(*arguments):
'--destination', '--destination',
dest='destination_filename', dest='destination_filename',
default=DEFAULT_DESTINATION_CONFIG_FILENAME, default=DEFAULT_DESTINATION_CONFIG_FILENAME,
help='Destination YAML configuration file. Default: {}'.format( help='Destination YAML configuration file, default: {}'.format(
DEFAULT_DESTINATION_CONFIG_FILENAME 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) return parser.parse_args(arguments)
@ -36,7 +42,10 @@ def main(): # pragma: no cover
args = parse_arguments(*sys.argv[1:]) args = parse_arguments(*sys.argv[1:])
generate.generate_sample_configuration( 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)) print('Generated a sample configuration file at {}.'.format(args.destination_filename))

View file

@ -109,13 +109,18 @@ def render_configuration(config):
return rendered.getvalue() 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 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): if not overwrite and os.path.exists(config_filename):
raise FileExistsError('{} already exists. Aborting.'.format(config_filename)) raise FileExistsError(
'{} already exists. Aborting. Use --overwrite to replace the file.'.format(
config_filename
)
)
try: try:
os.makedirs(os.path.dirname(config_filename), mode=0o700) 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 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 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, filename, the path to a schema filename in a YAML rendition of the JSON Schema format, and
write out a sample configuration file based on that schema. If a source filename is provided, whether to overwrite a destination file, write out a sample configuration file based on that
merge the parsed contents of that configuration into the generated configuration. 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)) schema = yaml.round_trip_load(open(schema_filename))
source_config = None source_config = None
@ -284,4 +292,5 @@ def generate_sample_configuration(source_filename, destination_filename, schema_
write_configuration( write_configuration(
destination_filename, destination_filename,
_comment_out_optional_configuration(render_configuration(destination_config)), _comment_out_optional_configuration(render_configuration(destination_config)),
overwrite=overwrite,
) )

View file

@ -1,13 +1,25 @@
from borgmatic.commands import generate_config as module 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() parser = module.parse_arguments()
assert parser.destination_filename == module.DEFAULT_DESTINATION_CONFIG_FILENAME 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') parser = module.parse_arguments('--destination', 'config.yaml')
assert parser.destination_filename == '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

View file

@ -87,7 +87,7 @@ location:
assert module._comment_out_optional_configuration(config.strip()) == expected_config.strip() 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'}) yaml_string = module.render_configuration({'foo': 'bar'})
assert yaml_string == 'foo: bar\n' 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') 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(): def test_write_configuration_with_already_existing_directory_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').and_raise(FileExistsError) flexmock(os).should_receive('makedirs').and_raise(FileExistsError)