Remove upgrade-borgmatic-config command for upgrading borgmatic 1.1.0 INI-style configuration (#529).
This commit is contained in:
parent
6098005f5d
commit
b10aee3070
7 changed files with 7 additions and 395 deletions
2
NEWS
2
NEWS
|
@ -1,5 +1,7 @@
|
|||
1.7.15.dev0
|
||||
* #399: Add a documentation troubleshooting note for MySQL/MariaDB authentication errors.
|
||||
* #529: Remove upgrade-borgmatic-config command for upgrading borgmatic 1.1.0 INI-style
|
||||
configuration.
|
||||
* #697, #712: Extract borgmatic configuration from backup via "bootstrap" action—even when
|
||||
borgmatic has no configuration yet!
|
||||
* #669: Add sample systemd user service for running borgmatic as a non-root user.
|
||||
|
|
|
@ -1,102 +0,0 @@
|
|||
import os
|
||||
import sys
|
||||
import textwrap
|
||||
from argparse import ArgumentParser
|
||||
|
||||
from ruamel import yaml
|
||||
|
||||
from borgmatic.config import convert, generate, legacy, validate
|
||||
|
||||
DEFAULT_SOURCE_CONFIG_FILENAME = '/etc/borgmatic/config'
|
||||
DEFAULT_SOURCE_EXCLUDES_FILENAME = '/etc/borgmatic/excludes'
|
||||
DEFAULT_DESTINATION_CONFIG_FILENAME = '/etc/borgmatic/config.yaml'
|
||||
|
||||
|
||||
def parse_arguments(*arguments):
|
||||
'''
|
||||
Given command-line arguments with which this script was invoked, parse the arguments and return
|
||||
them as an ArgumentParser instance.
|
||||
'''
|
||||
parser = ArgumentParser(
|
||||
description='''
|
||||
Convert legacy INI-style borgmatic configuration and excludes files to a single YAML
|
||||
configuration file. Note that this replaces any comments from the source files.
|
||||
'''
|
||||
)
|
||||
parser.add_argument(
|
||||
'-s',
|
||||
'--source-config',
|
||||
dest='source_config_filename',
|
||||
default=DEFAULT_SOURCE_CONFIG_FILENAME,
|
||||
help=f'Source INI-style configuration filename. Default: {DEFAULT_SOURCE_CONFIG_FILENAME}',
|
||||
)
|
||||
parser.add_argument(
|
||||
'-e',
|
||||
'--source-excludes',
|
||||
dest='source_excludes_filename',
|
||||
default=DEFAULT_SOURCE_EXCLUDES_FILENAME
|
||||
if os.path.exists(DEFAULT_SOURCE_EXCLUDES_FILENAME)
|
||||
else None,
|
||||
help='Excludes filename',
|
||||
)
|
||||
parser.add_argument(
|
||||
'-d',
|
||||
'--destination-config',
|
||||
dest='destination_config_filename',
|
||||
default=DEFAULT_DESTINATION_CONFIG_FILENAME,
|
||||
help=f'Destination YAML configuration filename. Default: {DEFAULT_DESTINATION_CONFIG_FILENAME}',
|
||||
)
|
||||
|
||||
return parser.parse_args(arguments)
|
||||
|
||||
|
||||
TEXT_WRAP_CHARACTERS = 80
|
||||
|
||||
|
||||
def display_result(args): # pragma: no cover
|
||||
result_lines = textwrap.wrap(
|
||||
f'Your borgmatic configuration has been upgraded. Please review the result in {args.destination_config_filename}.',
|
||||
TEXT_WRAP_CHARACTERS,
|
||||
)
|
||||
|
||||
excludes_phrase = (
|
||||
f' and {args.source_excludes_filename}' if args.source_excludes_filename else ''
|
||||
)
|
||||
delete_lines = textwrap.wrap(
|
||||
f'Once you are satisfied, you can safely delete {args.source_config_filename}{excludes_phrase}.',
|
||||
TEXT_WRAP_CHARACTERS,
|
||||
)
|
||||
|
||||
print('\n'.join(result_lines))
|
||||
print()
|
||||
print('\n'.join(delete_lines))
|
||||
|
||||
|
||||
def main(): # pragma: no cover
|
||||
try:
|
||||
args = parse_arguments(*sys.argv[1:])
|
||||
schema = yaml.round_trip_load(open(validate.schema_filename()).read())
|
||||
source_config = legacy.parse_configuration(
|
||||
args.source_config_filename, legacy.CONFIG_FORMAT
|
||||
)
|
||||
source_config_file_mode = os.stat(args.source_config_filename).st_mode
|
||||
source_excludes = (
|
||||
open(args.source_excludes_filename).read().splitlines()
|
||||
if args.source_excludes_filename
|
||||
else []
|
||||
)
|
||||
|
||||
destination_config = convert.convert_legacy_parsed_config(
|
||||
source_config, source_excludes, schema
|
||||
)
|
||||
|
||||
generate.write_configuration(
|
||||
args.destination_config_filename,
|
||||
generate.render_configuration(destination_config),
|
||||
mode=source_config_file_mode,
|
||||
)
|
||||
|
||||
display_result(args)
|
||||
except (ValueError, OSError) as error:
|
||||
print(error, file=sys.stderr)
|
||||
sys.exit(1)
|
|
@ -1,95 +0,0 @@
|
|||
import os
|
||||
|
||||
from ruamel import yaml
|
||||
|
||||
from borgmatic.config import generate
|
||||
|
||||
|
||||
def _convert_section(source_section_config, section_schema):
|
||||
'''
|
||||
Given a legacy Parsed_config instance for a single section, convert it to its corresponding
|
||||
yaml.comments.CommentedMap representation in preparation for actual serialization to YAML.
|
||||
|
||||
Where integer types exist in the given section schema, convert their values to integers.
|
||||
'''
|
||||
destination_section_config = yaml.comments.CommentedMap(
|
||||
[
|
||||
(
|
||||
option_name,
|
||||
int(option_value)
|
||||
if section_schema['properties'].get(option_name, {}).get('type') == 'integer'
|
||||
else option_value,
|
||||
)
|
||||
for option_name, option_value in source_section_config.items()
|
||||
]
|
||||
)
|
||||
|
||||
return destination_section_config
|
||||
|
||||
|
||||
def convert_legacy_parsed_config(source_config, source_excludes, schema):
|
||||
'''
|
||||
Given a legacy Parsed_config instance loaded from an INI-style config file and a list of exclude
|
||||
patterns, convert them to a corresponding yaml.comments.CommentedMap representation in
|
||||
preparation for serialization to a single YAML config file.
|
||||
|
||||
Additionally, use the given schema as a source of helpful comments to include within the
|
||||
returned CommentedMap.
|
||||
'''
|
||||
destination_config = yaml.comments.CommentedMap(
|
||||
[
|
||||
(section_name, _convert_section(section_config, schema['properties'][section_name]))
|
||||
for section_name, section_config in source_config._asdict().items()
|
||||
]
|
||||
)
|
||||
|
||||
# Split space-separated values into actual lists, make "repository" into a list, and merge in
|
||||
# excludes.
|
||||
location = destination_config['location']
|
||||
location['source_directories'] = source_config.location['source_directories'].split(' ')
|
||||
location['repositories'] = [location.pop('repository')]
|
||||
location['exclude_patterns'] = source_excludes
|
||||
|
||||
if source_config.consistency.get('checks'):
|
||||
destination_config['consistency']['checks'] = source_config.consistency['checks'].split(' ')
|
||||
|
||||
# Add comments to each section, and then add comments to the fields in each section.
|
||||
generate.add_comments_to_configuration_object(destination_config, schema)
|
||||
|
||||
for section_name, section_config in destination_config.items():
|
||||
generate.add_comments_to_configuration_object(
|
||||
section_config, schema['properties'][section_name], indent=generate.INDENT
|
||||
)
|
||||
|
||||
return destination_config
|
||||
|
||||
|
||||
class Legacy_configuration_not_upgraded(FileNotFoundError):
|
||||
def __init__(self):
|
||||
super(Legacy_configuration_not_upgraded, self).__init__(
|
||||
'''borgmatic changed its configuration file format in version 1.1.0 from INI-style
|
||||
to YAML. This better supports validation, and has a more natural way to express
|
||||
lists of values. To upgrade your existing configuration, run:
|
||||
|
||||
sudo upgrade-borgmatic-config
|
||||
|
||||
That will generate a new YAML configuration file at /etc/borgmatic/config.yaml
|
||||
(by default) using the values from both your existing configuration and excludes
|
||||
files. The new version of borgmatic will consume the YAML configuration file
|
||||
instead of the old one.'''
|
||||
)
|
||||
|
||||
|
||||
def guard_configuration_upgraded(source_config_filename, destination_config_filenames):
|
||||
'''
|
||||
If legacy source configuration exists but no destination upgraded configs do, raise
|
||||
Legacy_configuration_not_upgraded.
|
||||
|
||||
The idea is that we want to alert the user about upgrading their config if they haven't already.
|
||||
'''
|
||||
destination_config_exists = any(
|
||||
os.path.exists(filename) for filename in destination_config_filenames
|
||||
)
|
||||
|
||||
if os.path.exists(source_config_filename) and not destination_config_exists:
|
||||
raise Legacy_configuration_not_upgraded()
|
|
@ -61,21 +61,22 @@ and, if desired, replace your original configuration file with it.
|
|||
borgmatic changed its configuration file format in version 1.1.0 from
|
||||
INI-style to YAML. This better supports validation, and has a more natural way
|
||||
to express lists of values. To upgrade your existing configuration, first
|
||||
upgrade to the new version of borgmatic.
|
||||
upgrade to the last version of borgmatic to support converting configuration:
|
||||
borgmatic 1.7.14.
|
||||
|
||||
As of version 1.1.0, borgmatic no longer supports Python 2. If you were
|
||||
already running borgmatic with Python 3, then you can upgrade borgmatic
|
||||
in-place:
|
||||
|
||||
```bash
|
||||
sudo pip3 install --user --upgrade borgmatic
|
||||
sudo pip3 install --user --upgrade borgmatic==1.7.14
|
||||
```
|
||||
|
||||
But if you were running borgmatic with Python 2, uninstall and reinstall instead:
|
||||
|
||||
```bash
|
||||
sudo pip uninstall borgmatic
|
||||
sudo pip3 install --user borgmatic
|
||||
sudo pip3 install --user borgmatic==1.7.14
|
||||
```
|
||||
|
||||
The pip binary names for different versions of Python can differ, so the above
|
||||
|
@ -93,29 +94,12 @@ That will generate a new YAML configuration file at /etc/borgmatic/config.yaml
|
|||
excludes files. The new version of borgmatic will consume the YAML
|
||||
configuration file instead of the old one.
|
||||
|
||||
|
||||
### Upgrading from atticmatic
|
||||
|
||||
You can ignore this section if you're not an atticmatic user (the former name
|
||||
of borgmatic).
|
||||
|
||||
borgmatic only supports Borg now and no longer supports Attic. So if you're
|
||||
an Attic user, consider switching to Borg. See the [Borg upgrade
|
||||
command](https://borgbackup.readthedocs.io/en/stable/usage.html#borg-upgrade)
|
||||
for more information. Then, follow the instructions above about setting up
|
||||
your borgmatic configuration files.
|
||||
|
||||
If you were already using Borg with atticmatic, then you can upgrade
|
||||
from atticmatic to borgmatic by running the following commands:
|
||||
Now you can upgrade to a newer version of borgmatic:
|
||||
|
||||
```bash
|
||||
sudo pip3 uninstall atticmatic
|
||||
sudo pip3 install --user borgmatic
|
||||
```
|
||||
|
||||
That's it! borgmatic will continue using your /etc/borgmatic configuration
|
||||
files.
|
||||
|
||||
|
||||
## Upgrading Borg
|
||||
|
||||
|
|
1
setup.py
1
setup.py
|
@ -23,7 +23,6 @@ setup(
|
|||
entry_points={
|
||||
'console_scripts': [
|
||||
'borgmatic = borgmatic.commands.borgmatic:main',
|
||||
'upgrade-borgmatic-config = borgmatic.commands.convert_config:main',
|
||||
'generate-borgmatic-config = borgmatic.commands.generate_config:main',
|
||||
'validate-borgmatic-config = borgmatic.commands.validate_config:main',
|
||||
]
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
import os
|
||||
|
||||
import pytest
|
||||
from flexmock import flexmock
|
||||
|
||||
from borgmatic.commands import convert_config as module
|
||||
|
||||
|
||||
def test_parse_arguments_with_no_arguments_uses_defaults():
|
||||
flexmock(os.path).should_receive('exists').and_return(True)
|
||||
|
||||
parser = module.parse_arguments()
|
||||
|
||||
assert parser.source_config_filename == module.DEFAULT_SOURCE_CONFIG_FILENAME
|
||||
assert parser.source_excludes_filename == module.DEFAULT_SOURCE_EXCLUDES_FILENAME
|
||||
assert parser.destination_config_filename == module.DEFAULT_DESTINATION_CONFIG_FILENAME
|
||||
|
||||
|
||||
def test_parse_arguments_with_filename_arguments_overrides_defaults():
|
||||
flexmock(os.path).should_receive('exists').and_return(True)
|
||||
|
||||
parser = module.parse_arguments(
|
||||
'--source-config',
|
||||
'config',
|
||||
'--source-excludes',
|
||||
'excludes',
|
||||
'--destination-config',
|
||||
'config.yaml',
|
||||
)
|
||||
|
||||
assert parser.source_config_filename == 'config'
|
||||
assert parser.source_excludes_filename == 'excludes'
|
||||
assert parser.destination_config_filename == 'config.yaml'
|
||||
|
||||
|
||||
def test_parse_arguments_with_missing_default_excludes_file_sets_filename_to_none():
|
||||
flexmock(os.path).should_receive('exists').and_return(False)
|
||||
|
||||
parser = module.parse_arguments()
|
||||
|
||||
assert parser.source_config_filename == module.DEFAULT_SOURCE_CONFIG_FILENAME
|
||||
assert parser.source_excludes_filename is None
|
||||
assert parser.destination_config_filename == module.DEFAULT_DESTINATION_CONFIG_FILENAME
|
||||
|
||||
|
||||
def test_parse_arguments_with_invalid_arguments_exits():
|
||||
flexmock(os.path).should_receive('exists').and_return(True)
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
module.parse_arguments('--posix-me-harder')
|
|
@ -1,126 +0,0 @@
|
|||
import os
|
||||
from collections import OrderedDict, defaultdict, namedtuple
|
||||
|
||||
import pytest
|
||||
from flexmock import flexmock
|
||||
|
||||
from borgmatic.config import convert as module
|
||||
|
||||
Parsed_config = namedtuple('Parsed_config', ('location', 'storage', 'retention', 'consistency'))
|
||||
|
||||
|
||||
def test_convert_section_generates_integer_value_for_integer_type_in_schema():
|
||||
flexmock(module.yaml.comments).should_receive('CommentedMap').replace_with(OrderedDict)
|
||||
source_section_config = OrderedDict([('check_last', '3')])
|
||||
section_schema = {'type': 'object', 'properties': {'check_last': {'type': 'integer'}}}
|
||||
|
||||
destination_config = module._convert_section(source_section_config, section_schema)
|
||||
|
||||
assert destination_config == OrderedDict([('check_last', 3)])
|
||||
|
||||
|
||||
def test_convert_legacy_parsed_config_transforms_source_config_to_mapping():
|
||||
flexmock(module.yaml.comments).should_receive('CommentedMap').replace_with(OrderedDict)
|
||||
flexmock(module.generate).should_receive('add_comments_to_configuration_object')
|
||||
source_config = Parsed_config(
|
||||
location=OrderedDict([('source_directories', '/home'), ('repository', 'hostname.borg')]),
|
||||
storage=OrderedDict([('encryption_passphrase', 'supersecret')]),
|
||||
retention=OrderedDict([('keep_daily', 7)]),
|
||||
consistency=OrderedDict([('checks', 'repository')]),
|
||||
)
|
||||
source_excludes = ['/var']
|
||||
schema = {
|
||||
'type': 'object',
|
||||
'properties': defaultdict(lambda: {'type': 'object', 'properties': {}}),
|
||||
}
|
||||
|
||||
destination_config = module.convert_legacy_parsed_config(source_config, source_excludes, schema)
|
||||
|
||||
assert destination_config == OrderedDict(
|
||||
[
|
||||
(
|
||||
'location',
|
||||
OrderedDict(
|
||||
[
|
||||
('source_directories', ['/home']),
|
||||
('repositories', ['hostname.borg']),
|
||||
('exclude_patterns', ['/var']),
|
||||
]
|
||||
),
|
||||
),
|
||||
('storage', OrderedDict([('encryption_passphrase', 'supersecret')])),
|
||||
('retention', OrderedDict([('keep_daily', 7)])),
|
||||
('consistency', OrderedDict([('checks', ['repository'])])),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_convert_legacy_parsed_config_splits_space_separated_values():
|
||||
flexmock(module.yaml.comments).should_receive('CommentedMap').replace_with(OrderedDict)
|
||||
flexmock(module.generate).should_receive('add_comments_to_configuration_object')
|
||||
source_config = Parsed_config(
|
||||
location=OrderedDict(
|
||||
[('source_directories', '/home /etc'), ('repository', 'hostname.borg')]
|
||||
),
|
||||
storage=OrderedDict(),
|
||||
retention=OrderedDict(),
|
||||
consistency=OrderedDict([('checks', 'repository archives')]),
|
||||
)
|
||||
source_excludes = ['/var']
|
||||
schema = {
|
||||
'type': 'object',
|
||||
'properties': defaultdict(lambda: {'type': 'object', 'properties': {}}),
|
||||
}
|
||||
|
||||
destination_config = module.convert_legacy_parsed_config(source_config, source_excludes, schema)
|
||||
|
||||
assert destination_config == OrderedDict(
|
||||
[
|
||||
(
|
||||
'location',
|
||||
OrderedDict(
|
||||
[
|
||||
('source_directories', ['/home', '/etc']),
|
||||
('repositories', ['hostname.borg']),
|
||||
('exclude_patterns', ['/var']),
|
||||
]
|
||||
),
|
||||
),
|
||||
('storage', OrderedDict()),
|
||||
('retention', OrderedDict()),
|
||||
('consistency', OrderedDict([('checks', ['repository', 'archives'])])),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_guard_configuration_upgraded_raises_when_only_source_config_present():
|
||||
flexmock(os.path).should_receive('exists').with_args('config').and_return(True)
|
||||
flexmock(os.path).should_receive('exists').with_args('config.yaml').and_return(False)
|
||||
flexmock(os.path).should_receive('exists').with_args('other.yaml').and_return(False)
|
||||
|
||||
with pytest.raises(module.Legacy_configuration_not_upgraded):
|
||||
module.guard_configuration_upgraded('config', ('config.yaml', 'other.yaml'))
|
||||
|
||||
|
||||
def test_guard_configuration_upgraded_does_not_raise_when_only_destination_config_present():
|
||||
flexmock(os.path).should_receive('exists').with_args('config').and_return(False)
|
||||
flexmock(os.path).should_receive('exists').with_args('config.yaml').and_return(False)
|
||||
flexmock(os.path).should_receive('exists').with_args('other.yaml').and_return(True)
|
||||
|
||||
module.guard_configuration_upgraded('config', ('config.yaml', 'other.yaml'))
|
||||
|
||||
|
||||
def test_guard_configuration_upgraded_does_not_raise_when_both_configs_present():
|
||||
flexmock(os.path).should_receive('exists').with_args('config').and_return(True)
|
||||
flexmock(os.path).should_receive('exists').with_args('config.yaml').and_return(False)
|
||||
flexmock(os.path).should_receive('exists').with_args('other.yaml').and_return(True)
|
||||
|
||||
module.guard_configuration_upgraded('config', ('config.yaml', 'other.yaml'))
|
||||
|
||||
|
||||
def test_guard_configuration_upgraded_does_not_raise_when_neither_config_present():
|
||||
flexmock(os.path).should_receive('exists').with_args('config').and_return(False)
|
||||
flexmock(os.path).should_receive('exists').with_args('config.yaml').and_return(False)
|
||||
flexmock(os.path).should_receive('exists').with_args('other.yaml').and_return(False)
|
||||
|
||||
module.guard_configuration_upgraded('config', ('config.yaml', 'other.yaml'))
|
Loading…
Reference in a new issue