Compare commits

..
7 Commits
10 changed files with 155 additions and 33 deletions
+7
View File
@@ -1,3 +1,10 @@
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.
1.2.3
* #64, #90, #92: Rewrite of logging system. Now verbosity flags passed to Borg are derived from
borgmatic's log level. Note that the output of borgmatic might slightly change.
+3 -3
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)
@@ -306,8 +306,8 @@ borgmatic --list
borgmatic --info
```
You can include an optional `--json` flag with either `--list` or `--info` to
get the output formatted as JSON.
You can include an optional `--json` flag with `--create`, `--list`, or
`--info` to get the output formatted as JSON.
## Autopilot
+2 -2
View File
@@ -60,9 +60,9 @@ def _make_check_flags(checks, check_last=None, prefix=None):
last_flags = ()
prefix_flags = ()
if check_last:
logger.warn('Ignoring check_last option, as "archives" is not in consistency checks.')
logger.warning('Ignoring check_last option, as "archives" is not in consistency checks.')
if prefix:
logger.warn('Ignoring consistency prefix option, as "archives" is not in consistency checks.')
logger.warning('Ignoring consistency prefix option, as "archives" is not in consistency checks.')
if set(DEFAULT_CHECKS).issubset(set(checks)):
return last_flags + prefix_flags
+3 -4
View File
@@ -81,7 +81,7 @@ def parse_arguments(*arguments):
dest='json',
default=False,
action='store_true',
help='Output results from the --list or --info options as json',
help='Output results from the --create, --list, or --info options as json',
)
parser.add_argument(
'-n', '--dry-run',
@@ -100,7 +100,7 @@ def parse_arguments(*arguments):
args = parser.parse_args(arguments)
if args.json and not (args.create or args.list or args.info):
raise ValueError('The --json option can only be used with the --create, --list or --info options')
raise ValueError('The --json option can only be used with the --create, --list, or --info options')
if args.json and args.list and args.info:
raise ValueError(
@@ -191,8 +191,7 @@ def _run_commands_on_repository(
storage,
consistency,
local_path=local_path,
remote_path=remote_path,
json=args.json
remote_path=remote_path
)
if args.list:
logger.info('{}: Listing archives'.format(repository))
+57 -6
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))
)
+10 -10
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
@@ -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')
@@ -8,7 +8,7 @@ from borgmatic.config import legacy as module
def test_parse_section_options_with_punctuation_should_return_section_options():
parser = module.RawConfigParser()
parser.readfp(StringIO('[section]\nfoo: {}\n'.format(string.punctuation)))
parser.read_file(StringIO('[section]\nfoo: {}\n'.format(string.punctuation)))
section_format = module.Section_format(
'section',
+1 -1
View File
@@ -1,7 +1,7 @@
from setuptools import setup, find_packages
VERSION = '1.2.3'
VERSION = '1.2.5'
setup(
+2 -2
View File
@@ -1,5 +1,5 @@
flexmock==0.10.2
pykwalify==1.6.1
pytest==3.7.4
pytest-cov==2.5.1
pytest==3.8.1
pytest-cov==2.6.0
ruamel.yaml>0.15.0,<0.16.0