mirror of
https://github.com/borgmatic-collective/borgmatic.git
synced 2026-08-06 08:13:01 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
984702b3b2 | ||
|
|
1bc71e1c5d | ||
|
|
47efa88c9d | ||
|
|
3821636b77 | ||
|
|
596f6f9dac | ||
|
|
7ecdaea83a | ||
|
|
98cb2644db |
@@ -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
|
1.2.3
|
||||||
* #64, #90, #92: Rewrite of logging system. Now verbosity flags passed to Borg are derived from
|
* #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.
|
borgmatic's log level. Note that the output of borgmatic might slightly change.
|
||||||
|
|||||||
@@ -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
|
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
|
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
|
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
|
You can also have a look at the [full configuration
|
||||||
schema](https://projects.torsion.org/witten/borgmatic/src/master/borgmatic/config/schema.yaml)
|
schema](https://projects.torsion.org/witten/borgmatic/src/master/borgmatic/config/schema.yaml)
|
||||||
@@ -306,8 +306,8 @@ borgmatic --list
|
|||||||
borgmatic --info
|
borgmatic --info
|
||||||
```
|
```
|
||||||
|
|
||||||
You can include an optional `--json` flag with either `--list` or `--info` to
|
You can include an optional `--json` flag with `--create`, `--list`, or
|
||||||
get the output formatted as JSON.
|
`--info` to get the output formatted as JSON.
|
||||||
|
|
||||||
|
|
||||||
## Autopilot
|
## Autopilot
|
||||||
|
|||||||
@@ -60,9 +60,9 @@ def _make_check_flags(checks, check_last=None, prefix=None):
|
|||||||
last_flags = ()
|
last_flags = ()
|
||||||
prefix_flags = ()
|
prefix_flags = ()
|
||||||
if check_last:
|
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:
|
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)):
|
if set(DEFAULT_CHECKS).issubset(set(checks)):
|
||||||
return last_flags + prefix_flags
|
return last_flags + prefix_flags
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ def parse_arguments(*arguments):
|
|||||||
dest='json',
|
dest='json',
|
||||||
default=False,
|
default=False,
|
||||||
action='store_true',
|
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(
|
parser.add_argument(
|
||||||
'-n', '--dry-run',
|
'-n', '--dry-run',
|
||||||
@@ -100,7 +100,7 @@ def parse_arguments(*arguments):
|
|||||||
args = parser.parse_args(arguments)
|
args = parser.parse_args(arguments)
|
||||||
|
|
||||||
if args.json and not (args.create or args.list or args.info):
|
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:
|
if args.json and args.list and args.info:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
@@ -191,8 +191,7 @@ def _run_commands_on_repository(
|
|||||||
storage,
|
storage,
|
||||||
consistency,
|
consistency,
|
||||||
local_path=local_path,
|
local_path=local_path,
|
||||||
remote_path=remote_path,
|
remote_path=remote_path
|
||||||
json=args.json
|
|
||||||
)
|
)
|
||||||
if args.list:
|
if args.list:
|
||||||
logger.info('{}: Listing archives'.format(repository))
|
logger.info('{}: Listing archives'.format(repository))
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ INDENT = 4
|
|||||||
|
|
||||||
def _insert_newline_before_comment(config, field_name):
|
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.
|
field and its comments.
|
||||||
'''
|
'''
|
||||||
config.ca.items[field_name][1].insert(
|
config.ca.items[field_name][1].insert(
|
||||||
@@ -40,10 +40,58 @@ def _schema_to_sample_configuration(schema, level=0):
|
|||||||
return config
|
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
|
Post-process a rendered configuration string to comment out optional key/values. The idea is
|
||||||
config to file as YAML. Create any containing directories as needed.
|
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):
|
if os.path.exists(config_filename):
|
||||||
raise FileExistsError('{} already exists. Aborting.'.format(config_filename))
|
raise FileExistsError('{} already exists. Aborting.'.format(config_filename))
|
||||||
@@ -54,7 +102,7 @@ def write_configuration(config_filename, config, mode=0o600):
|
|||||||
pass
|
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(rendered_config)
|
||||||
|
|
||||||
os.chmod(config_filename, mode)
|
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))
|
schema = yaml.round_trip_load(open(schema_filename))
|
||||||
config = _schema_to_sample_configuration(schema)
|
config = _schema_to_sample_configuration(schema)
|
||||||
|
|
||||||
write_configuration(config_filename, config)
|
write_configuration(
|
||||||
|
config_filename,
|
||||||
|
_comment_out_optional_configuration(_render_configuration(config))
|
||||||
|
)
|
||||||
|
|||||||
@@ -18,6 +18,16 @@ map:
|
|||||||
- /home
|
- /home
|
||||||
- /etc
|
- /etc
|
||||||
- /var/log/syslog*
|
- /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:
|
one_file_system:
|
||||||
type: bool
|
type: bool
|
||||||
desc: Stay in same file system (do not cross mount points).
|
desc: Stay in same file system (do not cross mount points).
|
||||||
@@ -48,16 +58,6 @@ map:
|
|||||||
type: scalar
|
type: scalar
|
||||||
desc: Alternate Borg remote executable. Defaults to "borg".
|
desc: Alternate Borg remote executable. Defaults to "borg".
|
||||||
example: borg1
|
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:
|
patterns:
|
||||||
seq:
|
seq:
|
||||||
- type: scalar
|
- type: scalar
|
||||||
|
|||||||
@@ -16,6 +16,69 @@ def test_insert_newline_before_comment_does_not_raise():
|
|||||||
module._insert_newline_before_comment(config, field_name)
|
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():
|
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')
|
flexmock(os).should_receive('makedirs')
|
||||||
@@ -23,14 +86,14 @@ def test_write_configuration_does_not_raise():
|
|||||||
builtins.should_receive('open').and_return(StringIO())
|
builtins.should_receive('open').and_return(StringIO())
|
||||||
flexmock(os).should_receive('chmod')
|
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():
|
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)
|
||||||
|
|
||||||
with pytest.raises(FileExistsError):
|
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():
|
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())
|
builtins.should_receive('open').and_return(StringIO())
|
||||||
flexmock(os).should_receive('chmod')
|
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():
|
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():
|
def test_generate_sample_configuration_does_not_raise():
|
||||||
builtins = flexmock(sys.modules['builtins'])
|
builtins = flexmock(sys.modules['builtins'])
|
||||||
builtins.should_receive('open').with_args('schema.yaml').and_return('')
|
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('_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')
|
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():
|
def test_parse_section_options_with_punctuation_should_return_section_options():
|
||||||
parser = module.RawConfigParser()
|
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_format = module.Section_format(
|
||||||
'section',
|
'section',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
|
||||||
VERSION = '1.2.3'
|
VERSION = '1.2.5'
|
||||||
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
flexmock==0.10.2
|
flexmock==0.10.2
|
||||||
pykwalify==1.6.1
|
pykwalify==1.6.1
|
||||||
pytest==3.7.4
|
pytest==3.8.1
|
||||||
pytest-cov==2.5.1
|
pytest-cov==2.6.0
|
||||||
ruamel.yaml>0.15.0,<0.16.0
|
ruamel.yaml>0.15.0,<0.16.0
|
||||||
|
|||||||
Reference in New Issue
Block a user