In generate-borgmatic-config, comment out all optional config (#57).

This commit is contained in:
Dan Helfman 2018-09-29 15:03:11 -07:00
parent 3821636b77
commit 47efa88c9d
5 changed files with 141 additions and 21 deletions

4
NEWS
View file

@ -1,3 +1,7 @@
1.2.5
* #57: When generating sample configuration with generate-borgmatic-config, comment out all
optional configuration so as to streamline the initial configuration process.
1.2.4
* Fix for archive checking traceback due to parameter mismatch.

View file

@ -108,7 +108,7 @@ not in your system `PATH`. Try looking in `/usr/local/bin/`.
This generates a sample configuration file at /etc/borgmatic/config.yaml (by
default). You should edit the file to suit your needs, as the values are just
representative. All fields are optional except where indicated, so feel free
to remove anything you don't need.
to ignore anything you don't need.
You can also have a look at the [full configuration
schema](https://projects.torsion.org/witten/borgmatic/src/master/borgmatic/config/schema.yaml)

View file

@ -9,7 +9,7 @@ INDENT = 4
def _insert_newline_before_comment(config, field_name):
'''
Using some ruamel.yaml black magic, insert a blank line in the config right befor the given
Using some ruamel.yaml black magic, insert a blank line in the config right before the given
field and its comments.
'''
config.ca.items[field_name][1].insert(
@ -40,10 +40,58 @@ def _schema_to_sample_configuration(schema, level=0):
return config
def write_configuration(config_filename, config, mode=0o600):
def _comment_out_line(line):
# If it's already is commented out (or empty), there's nothing further to do!
stripped_line = line.lstrip()
if not stripped_line or stripped_line.startswith('#'):
return line
# Comment out the names of optional sections.
one_indent = ' ' * INDENT
if not line.startswith(one_indent):
return '#' + line
# Otherwise, comment out the line, but insert the "#" after the first indent for aesthetics.
return '#'.join((one_indent, line[INDENT:]))
def _comment_out_optional_configuration(rendered_config):
'''
Given a target config filename and a config data structure of nested OrderedDicts, write out the
config to file as YAML. Create any containing directories as needed.
Post-process a rendered configuration string to comment out optional key/values. The idea is
that this prevents the user from having to comment out a bunch of configuration they don't care
about to get to a minimal viable configuration file.
Ideally ruamel.yaml would support this during configuration generation, but it's not terribly
easy to accomplish that way.
'''
lines = []
required = False
for line in rendered_config.split('\n'):
# Upon encountering a required configuration option, skip commenting out lines until the
# next blank line.
stripped_line = line.strip()
if stripped_line in {'source_directories:', 'repositories:'} or line == 'location:':
required = True
elif not stripped_line:
required = False
lines.append(_comment_out_line(line) if not required else line)
return '\n'.join(lines)
def _render_configuration(config):
'''
Given a config data structure of nested OrderedDicts, render the config as YAML and return it.
'''
return yaml.round_trip_dump(config, indent=INDENT, block_seq_indent=INDENT)
def write_configuration(config_filename, rendered_config, mode=0o600):
'''
Given a target config filename and rendered config YAML, write it out to file. Create any
containing directories as needed.
'''
if os.path.exists(config_filename):
raise FileExistsError('{} already exists. Aborting.'.format(config_filename))
@ -54,7 +102,7 @@ def write_configuration(config_filename, config, mode=0o600):
pass
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(rendered_config)
os.chmod(config_filename, mode)
@ -90,4 +138,7 @@ def generate_sample_configuration(config_filename, schema_filename):
schema = yaml.round_trip_load(open(schema_filename))
config = _schema_to_sample_configuration(schema)
write_configuration(config_filename, config)
write_configuration(
config_filename,
_comment_out_optional_configuration(_render_configuration(config))
)

View file

@ -18,6 +18,16 @@ map:
- /home
- /etc
- /var/log/syslog*
repositories:
required: true
seq:
- type: scalar
desc: |
Paths to local or remote repositories (required). Tildes are expanded. Multiple
repositories are backed up to in sequence. See ssh_command for SSH options like
identity file or port.
example:
- user@backupserver:sourcehostname.borg
one_file_system:
type: bool
desc: Stay in same file system (do not cross mount points).
@ -48,16 +58,6 @@ map:
type: scalar
desc: Alternate Borg remote executable. Defaults to "borg".
example: borg1
repositories:
required: true
seq:
- type: scalar
desc: |
Paths to local or remote repositories (required). Tildes are expanded. Multiple
repositories are backed up to in sequence. See ssh_command for SSH options like
identity file or port.
example:
- user@backupserver:sourcehostname.borg
patterns:
seq:
- type: scalar

View file

@ -16,6 +16,69 @@ def test_insert_newline_before_comment_does_not_raise():
module._insert_newline_before_comment(config, field_name)
def test_comment_out_line_skips_blank_line():
line = ' \n'
assert module._comment_out_line(line) == line
def test_comment_out_line_skips_already_commented_out_line():
line = ' # foo'
assert module._comment_out_line(line) == line
def test_comment_out_line_comments_section_name():
line = 'figgy-pudding:'
assert module._comment_out_line(line) == '#' + line
def test_comment_out_line_comments_indented_option():
line = ' enabled: true'
assert module._comment_out_line(line) == ' #enabled: true'
def test_comment_out_optional_configuration_comments_optional_config_only():
flexmock(module)._comment_out_line = lambda line: '#' + line
config = '''
foo:
bar:
- baz
- quux
location:
repositories:
- one
- two
other: thing
'''
expected_config = '''
#foo:
# bar:
# - baz
# - quux
#
location:
repositories:
- one
- two
#
# other: thing
'''
assert module._comment_out_optional_configuration(config.strip()) == expected_config.strip()
def test_render_configuration_does_not_raise():
flexmock(module.yaml).should_receive('round_trip_dump')
module._render_configuration({})
def test_write_configuration_does_not_raise():
flexmock(os.path).should_receive('exists').and_return(False)
flexmock(os).should_receive('makedirs')
@ -23,14 +86,14 @@ def test_write_configuration_does_not_raise():
builtins.should_receive('open').and_return(StringIO())
flexmock(os).should_receive('chmod')
module.write_configuration('config.yaml', {})
module.write_configuration('config.yaml', 'config: yaml')
def test_write_configuration_with_already_existing_file_raises():
flexmock(os.path).should_receive('exists').and_return(True)
with pytest.raises(FileExistsError):
module.write_configuration('config.yaml', {})
module.write_configuration('config.yaml', 'config: yaml')
def test_write_configuration_with_already_existing_directory_does_not_raise():
@ -40,7 +103,7 @@ def test_write_configuration_with_already_existing_directory_does_not_raise():
builtins.should_receive('open').and_return(StringIO())
flexmock(os).should_receive('chmod')
module.write_configuration('config.yaml', {})
module.write_configuration('config.yaml', 'config: yaml')
def test_add_comments_to_configuration_does_not_raise():
@ -59,7 +122,9 @@ def test_add_comments_to_configuration_does_not_raise():
def test_generate_sample_configuration_does_not_raise():
builtins = flexmock(sys.modules['builtins'])
builtins.should_receive('open').with_args('schema.yaml').and_return('')
flexmock(module).should_receive('write_configuration')
flexmock(module).should_receive('_schema_to_sample_configuration')
flexmock(module).should_receive('_render_configuration')
flexmock(module).should_receive('_comment_out_optional_configuration')
flexmock(module).should_receive('write_configuration')
module.generate_sample_configuration('config.yaml', 'schema.yaml')