Deprecate generate-borgmatic-config in favor if new "config generate" action (#529).
This commit is contained in:
parent
803fc25848
commit
1b90da5bf1
16 changed files with 317 additions and 153 deletions
5
NEWS
5
NEWS
|
@ -2,8 +2,9 @@
|
||||||
* #399: Add a documentation troubleshooting note for MySQL/MariaDB authentication errors.
|
* #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
|
* #529: Remove upgrade-borgmatic-config command for upgrading borgmatic 1.1.0 INI-style
|
||||||
configuration.
|
configuration.
|
||||||
* #697, #712: Extract borgmatic configuration from backup via "bootstrap" action—even when
|
* #529: Deprecate generate-borgmatic-config in favor if new "config generate" action.
|
||||||
borgmatic has no configuration yet!
|
* #697, #712: Extract borgmatic configuration from backup via new "config bootstrap" action—even
|
||||||
|
when borgmatic has no configuration yet!
|
||||||
* #669: Add sample systemd user service for running borgmatic as a non-root user.
|
* #669: Add sample systemd user service for running borgmatic as a non-root user.
|
||||||
* #711, #713: Fix an error when "data" check time files are accessed without getting upgraded
|
* #711, #713: Fix an error when "data" check time files are accessed without getting upgraded
|
||||||
first.
|
first.
|
||||||
|
|
39
borgmatic/actions/config/generate.py
Normal file
39
borgmatic/actions/config/generate.py
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import borgmatic.config.generate
|
||||||
|
import borgmatic.config.validate
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def run_generate(generate_arguments, global_arguments):
|
||||||
|
dry_run_label = ' (dry run; not actually writing anything)' if global_arguments.dry_run else ''
|
||||||
|
|
||||||
|
logger.answer(
|
||||||
|
f'Generating a configuration file at: {generate_arguments.destination_filename}{dry_run_label}'
|
||||||
|
)
|
||||||
|
|
||||||
|
borgmatic.config.generate.generate_sample_configuration(
|
||||||
|
global_arguments.dry_run,
|
||||||
|
generate_arguments.source_filename,
|
||||||
|
generate_arguments.destination_filename,
|
||||||
|
borgmatic.config.validate.schema_filename(),
|
||||||
|
overwrite=generate_arguments.overwrite,
|
||||||
|
)
|
||||||
|
|
||||||
|
if generate_arguments.source_filename:
|
||||||
|
logger.answer(
|
||||||
|
f'''
|
||||||
|
Merged in the contents of configuration file at: {generate_arguments.source_filename}
|
||||||
|
To review the changes made, run:
|
||||||
|
|
||||||
|
diff --unified {generate_arguments.source_filename} {generate_arguments.destination_filename}'''
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.answer(
|
||||||
|
'''
|
||||||
|
This includes all available configuration options with example values, the few
|
||||||
|
required options as indicated. Please edit the file to suit your needs.
|
||||||
|
|
||||||
|
If you ever need help: https://torsion.org/borgmatic/#issues'''
|
||||||
|
)
|
|
@ -695,14 +695,12 @@ def make_parsers():
|
||||||
|
|
||||||
config_parsers = config_parser.add_subparsers(
|
config_parsers = config_parser.add_subparsers(
|
||||||
title='config sub-actions',
|
title='config sub-actions',
|
||||||
description='Valid sub-actions for config',
|
|
||||||
help='Additional help',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
config_bootstrap_parser = config_parsers.add_parser(
|
config_bootstrap_parser = config_parsers.add_parser(
|
||||||
'bootstrap',
|
'bootstrap',
|
||||||
help='Extract the config files used to create a borgmatic repository',
|
help='Extract the borgmatic config files from a named archive',
|
||||||
description='Extract config files that were used to create a borgmatic repository during the "create" action',
|
description='Extract the borgmatic config files from a named archive',
|
||||||
add_help=False,
|
add_help=False,
|
||||||
)
|
)
|
||||||
config_bootstrap_group = config_bootstrap_parser.add_argument_group(
|
config_bootstrap_group = config_bootstrap_parser.add_argument_group(
|
||||||
|
@ -746,6 +744,36 @@ def make_parsers():
|
||||||
'-h', '--help', action='help', help='Show this help message and exit'
|
'-h', '--help', action='help', help='Show this help message and exit'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
config_generate_parser = config_parsers.add_parser(
|
||||||
|
'generate',
|
||||||
|
help='Generate a sample borgmatic configuration file',
|
||||||
|
description='Generate a sample borgmatic configuration file',
|
||||||
|
add_help=False,
|
||||||
|
)
|
||||||
|
config_generate_group = config_generate_parser.add_argument_group('config generate arguments')
|
||||||
|
config_generate_group.add_argument(
|
||||||
|
'-s',
|
||||||
|
'--source',
|
||||||
|
dest='source_filename',
|
||||||
|
help='Optional configuration file to merge into the generated configuration, useful for upgrading your configuration',
|
||||||
|
)
|
||||||
|
config_generate_group.add_argument(
|
||||||
|
'-d',
|
||||||
|
'--destination',
|
||||||
|
dest='destination_filename',
|
||||||
|
default=config_paths[0],
|
||||||
|
help=f'Destination configuration file, default: {unexpanded_config_paths[0]}',
|
||||||
|
)
|
||||||
|
config_generate_group.add_argument(
|
||||||
|
'--overwrite',
|
||||||
|
default=False,
|
||||||
|
action='store_true',
|
||||||
|
help='Whether to overwrite any existing destination file, defaults to false',
|
||||||
|
)
|
||||||
|
config_generate_group.add_argument(
|
||||||
|
'-h', '--help', action='help', help='Show this help message and exit'
|
||||||
|
)
|
||||||
|
|
||||||
export_tar_parser = action_parsers.add_parser(
|
export_tar_parser = action_parsers.add_parser(
|
||||||
'export-tar',
|
'export-tar',
|
||||||
aliases=ACTION_ALIASES['export-tar'],
|
aliases=ACTION_ALIASES['export-tar'],
|
||||||
|
@ -1170,9 +1198,10 @@ def parse_arguments(*unparsed_arguments):
|
||||||
unparsed_arguments, action_parsers.choices
|
unparsed_arguments, action_parsers.choices
|
||||||
)
|
)
|
||||||
|
|
||||||
if 'bootstrap' in arguments.keys() and len(arguments.keys()) > 1:
|
for action_name in ('bootstrap', 'generate', 'validate'):
|
||||||
|
if action_name in arguments.keys() and len(arguments.keys()) > 1:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
'The bootstrap action cannot be combined with other actions. Please run it separately.'
|
'The {action_name} action cannot be combined with other actions. Please run it separately.'
|
||||||
)
|
)
|
||||||
|
|
||||||
arguments['global'] = top_level_parser.parse_args(remaining_arguments)
|
arguments['global'] = top_level_parser.parse_args(remaining_arguments)
|
||||||
|
|
|
@ -19,6 +19,7 @@ import borgmatic.actions.break_lock
|
||||||
import borgmatic.actions.check
|
import borgmatic.actions.check
|
||||||
import borgmatic.actions.compact
|
import borgmatic.actions.compact
|
||||||
import borgmatic.actions.config.bootstrap
|
import borgmatic.actions.config.bootstrap
|
||||||
|
import borgmatic.actions.config.generate
|
||||||
import borgmatic.actions.create
|
import borgmatic.actions.create
|
||||||
import borgmatic.actions.export_tar
|
import borgmatic.actions.export_tar
|
||||||
import borgmatic.actions.extract
|
import borgmatic.actions.extract
|
||||||
|
@ -602,19 +603,24 @@ def get_local_path(configs):
|
||||||
return next(iter(configs.values())).get('location', {}).get('local_path', 'borg')
|
return next(iter(configs.values())).get('location', {}).get('local_path', 'borg')
|
||||||
|
|
||||||
|
|
||||||
def collect_configuration_run_summary_logs(configs, arguments):
|
def collect_highlander_action_summary_logs(configs, arguments):
|
||||||
'''
|
'''
|
||||||
Given a dict of configuration filename to corresponding parsed configuration, and parsed
|
Given a dict of configuration filename to corresponding parsed configuration and parsed
|
||||||
command-line arguments as a dict from subparser name to a parsed namespace of arguments, run
|
command-line arguments as a dict from subparser name to a parsed namespace of arguments, run
|
||||||
each configuration file and yield a series of logging.LogRecord instances containing summary
|
a highlander action specified in the arguments, if any, and yield a series of logging.LogRecord
|
||||||
information about each run.
|
instances containing summary information.
|
||||||
|
|
||||||
As a side effect of running through these configuration files, output their JSON results, if
|
A highlander action is an action that cannot coexist with other actions on the borgmatic
|
||||||
any, to stdout.
|
command-line, and borgmatic exits after processing such an action.
|
||||||
'''
|
'''
|
||||||
if 'bootstrap' in arguments:
|
if 'bootstrap' in arguments:
|
||||||
|
try:
|
||||||
# No configuration file is needed for bootstrap.
|
# No configuration file is needed for bootstrap.
|
||||||
local_borg_version = borg_version.local_borg_version({}, 'borg')
|
local_borg_version = borg_version.local_borg_version({}, 'borg')
|
||||||
|
except (OSError, CalledProcessError, ValueError) as error:
|
||||||
|
yield from log_error_records('Error getting local Borg version', error)
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
borgmatic.actions.config.bootstrap.run_bootstrap(
|
borgmatic.actions.config.bootstrap.run_bootstrap(
|
||||||
arguments['bootstrap'], arguments['global'], local_borg_version
|
arguments['bootstrap'], arguments['global'], local_borg_version
|
||||||
|
@ -622,7 +628,7 @@ def collect_configuration_run_summary_logs(configs, arguments):
|
||||||
yield logging.makeLogRecord(
|
yield logging.makeLogRecord(
|
||||||
dict(
|
dict(
|
||||||
levelno=logging.ANSWER,
|
levelno=logging.ANSWER,
|
||||||
levelname='INFO',
|
levelname='ANSWER',
|
||||||
msg='Bootstrap successful',
|
msg='Bootstrap successful',
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -635,6 +641,38 @@ def collect_configuration_run_summary_logs(configs, arguments):
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if 'generate' in arguments:
|
||||||
|
try:
|
||||||
|
borgmatic.actions.config.generate.run_generate(
|
||||||
|
arguments['generate'], arguments['global']
|
||||||
|
)
|
||||||
|
yield logging.makeLogRecord(
|
||||||
|
dict(
|
||||||
|
levelno=logging.ANSWER,
|
||||||
|
levelname='ANSWER',
|
||||||
|
msg='Generate successful',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except (
|
||||||
|
CalledProcessError,
|
||||||
|
ValueError,
|
||||||
|
OSError,
|
||||||
|
) as error:
|
||||||
|
yield from log_error_records(error)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def collect_configuration_run_summary_logs(configs, arguments):
|
||||||
|
'''
|
||||||
|
Given a dict of configuration filename to corresponding parsed configuration and parsed
|
||||||
|
command-line arguments as a dict from subparser name to a parsed namespace of arguments, run
|
||||||
|
each configuration file and yield a series of logging.LogRecord instances containing summary
|
||||||
|
information about each run.
|
||||||
|
|
||||||
|
As a side effect of running through these configuration files, output their JSON results, if
|
||||||
|
any, to stdout.
|
||||||
|
'''
|
||||||
# Run cross-file validation checks.
|
# Run cross-file validation checks.
|
||||||
repository = None
|
repository = None
|
||||||
|
|
||||||
|
@ -730,7 +768,7 @@ def exit_with_help_link(): # pragma: no cover
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def main(): # pragma: no cover
|
def main(extra_summary_logs=[]): # pragma: no cover
|
||||||
configure_signals()
|
configure_signals()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -786,7 +824,14 @@ def main(): # pragma: no cover
|
||||||
|
|
||||||
logger.debug('Ensuring legacy configuration is upgraded')
|
logger.debug('Ensuring legacy configuration is upgraded')
|
||||||
|
|
||||||
summary_logs = parse_logs + list(collect_configuration_run_summary_logs(configs, arguments))
|
summary_logs = (
|
||||||
|
parse_logs
|
||||||
|
+ (
|
||||||
|
list(collect_highlander_action_summary_logs(configs, arguments))
|
||||||
|
or list(collect_configuration_run_summary_logs(configs, arguments))
|
||||||
|
)
|
||||||
|
+ extra_summary_logs
|
||||||
|
)
|
||||||
summary_logs_max_level = max(log.levelno for log in summary_logs)
|
summary_logs_max_level = max(log.levelno for log in summary_logs)
|
||||||
|
|
||||||
for message in ('', 'summary:'):
|
for message in ('', 'summary:'):
|
||||||
|
|
|
@ -167,6 +167,6 @@ def fish_completion():
|
||||||
f'''complete -c borgmatic -f -n "$exact_option_condition" -a '{' '.join(action.option_strings)}' -d {shlex.quote(action.help)} -n "__fish_seen_subcommand_from {action_name}"{exact_options_completion(action)}'''
|
f'''complete -c borgmatic -f -n "$exact_option_condition" -a '{' '.join(action.option_strings)}' -d {shlex.quote(action.help)} -n "__fish_seen_subcommand_from {action_name}"{exact_options_completion(action)}'''
|
||||||
for action_name, subparser in subparsers.choices.items()
|
for action_name, subparser in subparsers.choices.items()
|
||||||
for action in subparser._actions
|
for action in subparser._actions
|
||||||
if 'Deprecated' not in action.help
|
if 'Deprecated' not in (action.help or ())
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,63 +1,17 @@
|
||||||
|
import logging
|
||||||
import sys
|
import sys
|
||||||
from argparse import ArgumentParser
|
|
||||||
|
|
||||||
from borgmatic.config import generate, validate
|
import borgmatic.commands.borgmatic
|
||||||
|
|
||||||
DEFAULT_DESTINATION_CONFIG_FILENAME = '/etc/borgmatic/config.yaml'
|
|
||||||
|
|
||||||
|
|
||||||
def parse_arguments(*arguments):
|
def main():
|
||||||
'''
|
warning_log = logging.makeLogRecord(
|
||||||
Given command-line arguments with which this script was invoked, parse the arguments and return
|
dict(
|
||||||
them as an ArgumentParser instance.
|
levelno=logging.WARNING,
|
||||||
'''
|
levelname='WARNING',
|
||||||
parser = ArgumentParser(description='Generate a sample borgmatic YAML configuration file.')
|
msg='generate-borgmatic-config is deprecated and will be removed from a future release. Please use "borgmatic config generate" instead.',
|
||||||
parser.add_argument(
|
|
||||||
'-s',
|
|
||||||
'--source',
|
|
||||||
dest='source_filename',
|
|
||||||
help='Optional YAML configuration file to merge into the generated configuration, useful for upgrading your configuration',
|
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
|
||||||
'-d',
|
|
||||||
'--destination',
|
|
||||||
dest='destination_filename',
|
|
||||||
default=DEFAULT_DESTINATION_CONFIG_FILENAME,
|
|
||||||
help=f'Destination YAML configuration file, default: {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)
|
sys.argv = ['borgmatic', 'config', 'generate'] + sys.argv[1:]
|
||||||
|
borgmatic.commands.borgmatic.main([warning_log])
|
||||||
|
|
||||||
def main(): # pragma: no cover
|
|
||||||
try:
|
|
||||||
args = parse_arguments(*sys.argv[1:])
|
|
||||||
|
|
||||||
generate.generate_sample_configuration(
|
|
||||||
args.source_filename,
|
|
||||||
args.destination_filename,
|
|
||||||
validate.schema_filename(),
|
|
||||||
overwrite=args.overwrite,
|
|
||||||
)
|
|
||||||
|
|
||||||
print(f'Generated a sample configuration file at {args.destination_filename}.')
|
|
||||||
print()
|
|
||||||
if args.source_filename:
|
|
||||||
print(f'Merged in the contents of configuration file at {args.source_filename}.')
|
|
||||||
print('To review the changes made, run:')
|
|
||||||
print()
|
|
||||||
print(f' diff --unified {args.source_filename} {args.destination_filename}')
|
|
||||||
print()
|
|
||||||
print('This includes all available configuration options with example values. The few')
|
|
||||||
print('required options are indicated. Please edit the file to suit your needs.')
|
|
||||||
print()
|
|
||||||
print('If you ever need help: https://torsion.org/borgmatic/#issues')
|
|
||||||
except (ValueError, OSError) as error:
|
|
||||||
print(error, file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
|
@ -267,7 +267,7 @@ def merge_source_configuration_into_destination(destination_config, source_confi
|
||||||
|
|
||||||
|
|
||||||
def generate_sample_configuration(
|
def generate_sample_configuration(
|
||||||
source_filename, destination_filename, schema_filename, overwrite=False
|
dry_run, 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
|
||||||
|
@ -287,6 +287,9 @@ def generate_sample_configuration(
|
||||||
_schema_to_sample_configuration(schema), source_config
|
_schema_to_sample_configuration(schema), source_config
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if dry_run:
|
||||||
|
return
|
||||||
|
|
||||||
write_configuration(
|
write_configuration(
|
||||||
destination_filename,
|
destination_filename,
|
||||||
_comment_out_optional_configuration(render_configuration(destination_config)),
|
_comment_out_optional_configuration(render_configuration(destination_config)),
|
||||||
|
|
|
@ -4,7 +4,7 @@ COPY . /app
|
||||||
RUN apk add --no-cache py3-pip py3-ruamel.yaml py3-ruamel.yaml.clib
|
RUN apk add --no-cache py3-pip py3-ruamel.yaml py3-ruamel.yaml.clib
|
||||||
RUN pip install --no-cache /app && generate-borgmatic-config && chmod +r /etc/borgmatic/config.yaml
|
RUN pip install --no-cache /app && generate-borgmatic-config && chmod +r /etc/borgmatic/config.yaml
|
||||||
RUN borgmatic --help > /command-line.txt \
|
RUN borgmatic --help > /command-line.txt \
|
||||||
&& for action in rcreate transfer create prune compact check extract config "config bootstrap" export-tar mount umount restore rlist list rinfo info break-lock borg; do \
|
&& for action in rcreate transfer create prune compact check extract config "config bootstrap" "config generate" export-tar mount umount restore rlist list rinfo info break-lock borg; do \
|
||||||
echo -e "\n--------------------------------------------------------------------------------\n" >> /command-line.txt \
|
echo -e "\n--------------------------------------------------------------------------------\n" >> /command-line.txt \
|
||||||
&& borgmatic $action --help >> /command-line.txt; done
|
&& borgmatic $action --help >> /command-line.txt; done
|
||||||
|
|
||||||
|
|
|
@ -20,18 +20,22 @@ instance, for applications:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo mkdir /etc/borgmatic.d
|
sudo mkdir /etc/borgmatic.d
|
||||||
sudo generate-borgmatic-config --destination /etc/borgmatic.d/app1.yaml
|
sudo borgmatic config generate --destination /etc/borgmatic.d/app1.yaml
|
||||||
sudo generate-borgmatic-config --destination /etc/borgmatic.d/app2.yaml
|
sudo borgmatic config generate --destination /etc/borgmatic.d/app2.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
Or, for repositories:
|
Or, for repositories:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo mkdir /etc/borgmatic.d
|
sudo mkdir /etc/borgmatic.d
|
||||||
sudo generate-borgmatic-config --destination /etc/borgmatic.d/repo1.yaml
|
sudo borgmatic config generate --destination /etc/borgmatic.d/repo1.yaml
|
||||||
sudo generate-borgmatic-config --destination /etc/borgmatic.d/repo2.yaml
|
sudo borgmatic config generate --destination /etc/borgmatic.d/repo2.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<span class="minilink minilink-addedin">Prior to version 1.7.15</span> The
|
||||||
|
command to generate configuation files was `generate-borgmatic-config` instead
|
||||||
|
of `borgmatic config generate`.
|
||||||
|
|
||||||
When you set up multiple configuration files like this, borgmatic will run
|
When you set up multiple configuration files like this, borgmatic will run
|
||||||
each one in turn from a single borgmatic invocation. This includes, by
|
each one in turn from a single borgmatic invocation. This includes, by
|
||||||
default, the traditional `/etc/borgmatic/config.yaml` as well.
|
default, the traditional `/etc/borgmatic/config.yaml` as well.
|
||||||
|
|
|
@ -120,16 +120,24 @@ offerings, but do not currently fund borgmatic development or hosting.
|
||||||
|
|
||||||
After you install borgmatic, generate a sample configuration file:
|
After you install borgmatic, generate a sample configuration file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo borgmatic config generate
|
||||||
|
```
|
||||||
|
|
||||||
|
<span class="minilink minilink-addedin">Prior to version 1.7.15</span>
|
||||||
|
Generate a configuation file with this command instead:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo generate-borgmatic-config
|
sudo generate-borgmatic-config
|
||||||
```
|
```
|
||||||
|
|
||||||
If that command is not found, then it may be installed in a location that's
|
If neither command is found, then borgmatic may be installed in a location
|
||||||
not in your system `PATH` (see above). Try looking in `~/.local/bin/`.
|
that's not in your system `PATH` (see above). Try looking in `~/.local/bin/`.
|
||||||
|
|
||||||
This generates a sample configuration file at `/etc/borgmatic/config.yaml` by
|
The command generates a sample configuration file at
|
||||||
default. If you'd like to use another path, use the `--destination` flag, for
|
`/etc/borgmatic/config.yaml` by default. If you'd like to use another path,
|
||||||
instance: `--destination ~/.config/borgmatic/config.yaml`.
|
use the `--destination` flag, for instance: `--destination
|
||||||
|
~/.config/borgmatic/config.yaml`.
|
||||||
|
|
||||||
You should edit the configuration file to suit your needs, as the generated
|
You should edit the configuration file to suit your needs, as the generated
|
||||||
values are only representative. All options are optional except where
|
values are only representative. All options are optional except where
|
||||||
|
|
|
@ -29,29 +29,33 @@ configuration options. This is completely optional. If you prefer, you can add
|
||||||
new configuration options manually.
|
new configuration options manually.
|
||||||
|
|
||||||
If you do want to upgrade your configuration file to include new options, use
|
If you do want to upgrade your configuration file to include new options, use
|
||||||
the `generate-borgmatic-config` script with its optional `--source` flag that
|
the `borgmatic config generate` action with its optional `--source` flag that
|
||||||
takes the path to your original configuration file. If provided with this
|
takes the path to your original configuration file. If provided with this
|
||||||
path, `generate-borgmatic-config` merges your original configuration into the
|
path, `borgmatic config generate` merges your original configuration into the
|
||||||
generated configuration file, so you get all the newest options and comments.
|
generated configuration file, so you get all the newest options and comments.
|
||||||
|
|
||||||
Here's an example:
|
Here's an example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
generate-borgmatic-config --source config.yaml --destination config-new.yaml
|
borgmatic config generate --source config.yaml --destination config-new.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<span class="minilink minilink-addedin">Prior to version 1.7.15</span> The
|
||||||
|
command to generate configuation files was `generate-borgmatic-config` instead
|
||||||
|
of `borgmatic config generate`.
|
||||||
|
|
||||||
New options start as commented out, so you can edit the file and decide
|
New options start as commented out, so you can edit the file and decide
|
||||||
whether you want to use each one.
|
whether you want to use each one.
|
||||||
|
|
||||||
There are a few caveats to this process. First, when generating the new
|
There are a few caveats to this process. First, when generating the new
|
||||||
configuration file, `generate-borgmatic-config` replaces any comments you've
|
configuration file, `borgmatic config generate` replaces any comments you've
|
||||||
written in your original configuration file with the newest generated
|
written in your original configuration file with the newest generated
|
||||||
comments. Second, the script adds back any options you had originally deleted,
|
comments. Second, the script adds back any options you had originally deleted,
|
||||||
although it does so with the options commented out. And finally, any YAML
|
although it does so with the options commented out. And finally, any YAML
|
||||||
includes you've used in the source configuration get flattened out into a
|
includes you've used in the source configuration get flattened out into a
|
||||||
single generated file.
|
single generated file.
|
||||||
|
|
||||||
As a safety measure, `generate-borgmatic-config` refuses to modify
|
As a safety measure, `borgmatic config generate` refuses to modify
|
||||||
configuration files in-place. So it's up to you to review the generated file
|
configuration files in-place. So it's up to you to review the generated file
|
||||||
and, if desired, replace your original configuration file with it.
|
and, if desired, replace your original configuration file with it.
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,9 @@
|
||||||
|
from flexmock import flexmock
|
||||||
|
|
||||||
from borgmatic.commands import generate_config as module
|
from borgmatic.commands import generate_config as module
|
||||||
|
|
||||||
|
|
||||||
def test_parse_arguments_with_no_arguments_uses_default_destination():
|
def test_main_does_not_raise():
|
||||||
parser = module.parse_arguments()
|
flexmock(module.borgmatic.commands.borgmatic).should_receive('main')
|
||||||
|
|
||||||
assert parser.destination_filename == module.DEFAULT_DESTINATION_CONFIG_FILENAME
|
module.main()
|
||||||
|
|
||||||
|
|
||||||
def test_parse_arguments_with_destination_argument_overrides_default():
|
|
||||||
parser = module.parse_arguments('--destination', '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
|
|
||||||
|
|
|
@ -210,7 +210,7 @@ def test_generate_sample_configuration_does_not_raise():
|
||||||
flexmock(module).should_receive('_comment_out_optional_configuration')
|
flexmock(module).should_receive('_comment_out_optional_configuration')
|
||||||
flexmock(module).should_receive('write_configuration')
|
flexmock(module).should_receive('write_configuration')
|
||||||
|
|
||||||
module.generate_sample_configuration(None, 'dest.yaml', 'schema.yaml')
|
module.generate_sample_configuration(False, None, 'dest.yaml', 'schema.yaml')
|
||||||
|
|
||||||
|
|
||||||
def test_generate_sample_configuration_with_source_filename_does_not_raise():
|
def test_generate_sample_configuration_with_source_filename_does_not_raise():
|
||||||
|
@ -225,4 +225,17 @@ def test_generate_sample_configuration_with_source_filename_does_not_raise():
|
||||||
flexmock(module).should_receive('_comment_out_optional_configuration')
|
flexmock(module).should_receive('_comment_out_optional_configuration')
|
||||||
flexmock(module).should_receive('write_configuration')
|
flexmock(module).should_receive('write_configuration')
|
||||||
|
|
||||||
module.generate_sample_configuration('source.yaml', 'dest.yaml', 'schema.yaml')
|
module.generate_sample_configuration(False, 'source.yaml', 'dest.yaml', 'schema.yaml')
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate_sample_configuration_with_dry_run_does_not_write_file():
|
||||||
|
builtins = flexmock(sys.modules['builtins'])
|
||||||
|
builtins.should_receive('open').with_args('schema.yaml').and_return('')
|
||||||
|
flexmock(module.yaml).should_receive('round_trip_load')
|
||||||
|
flexmock(module).should_receive('_schema_to_sample_configuration')
|
||||||
|
flexmock(module).should_receive('merge_source_configuration_into_destination')
|
||||||
|
flexmock(module).should_receive('render_configuration')
|
||||||
|
flexmock(module).should_receive('_comment_out_optional_configuration')
|
||||||
|
flexmock(module).should_receive('write_configuration').never()
|
||||||
|
|
||||||
|
module.generate_sample_configuration(True, None, 'dest.yaml', 'schema.yaml')
|
||||||
|
|
|
@ -124,4 +124,5 @@ def test_run_bootstrap_does_not_raise():
|
||||||
flexmock(module.borgmatic.borg.rlist).should_receive('resolve_archive_name').and_return(
|
flexmock(module.borgmatic.borg.rlist).should_receive('resolve_archive_name').and_return(
|
||||||
'archive'
|
'archive'
|
||||||
)
|
)
|
||||||
|
|
||||||
module.run_bootstrap(bootstrap_arguments, global_arguments, local_borg_version)
|
module.run_bootstrap(bootstrap_arguments, global_arguments, local_borg_version)
|
||||||
|
|
39
tests/unit/actions/config/test_generate.py
Normal file
39
tests/unit/actions/config/test_generate.py
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
from flexmock import flexmock
|
||||||
|
|
||||||
|
from borgmatic.actions.config import generate as module
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_bootstrap_does_not_raise():
|
||||||
|
generate_arguments = flexmock(
|
||||||
|
source_filename=None,
|
||||||
|
destination_filename='destination.yaml',
|
||||||
|
overwrite=False,
|
||||||
|
)
|
||||||
|
global_arguments = flexmock(dry_run=False)
|
||||||
|
flexmock(module.borgmatic.config.generate).should_receive('generate_sample_configuration')
|
||||||
|
|
||||||
|
module.run_generate(generate_arguments, global_arguments)
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_bootstrap_with_dry_run_does_not_raise():
|
||||||
|
generate_arguments = flexmock(
|
||||||
|
source_filename=None,
|
||||||
|
destination_filename='destination.yaml',
|
||||||
|
overwrite=False,
|
||||||
|
)
|
||||||
|
global_arguments = flexmock(dry_run=True)
|
||||||
|
flexmock(module.borgmatic.config.generate).should_receive('generate_sample_configuration')
|
||||||
|
|
||||||
|
module.run_generate(generate_arguments, global_arguments)
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_bootstrap_with_source_filename_does_not_raise():
|
||||||
|
generate_arguments = flexmock(
|
||||||
|
source_filename='source.yaml',
|
||||||
|
destination_filename='destination.yaml',
|
||||||
|
overwrite=False,
|
||||||
|
)
|
||||||
|
global_arguments = flexmock(dry_run=False)
|
||||||
|
flexmock(module.borgmatic.config.generate).should_receive('generate_sample_configuration')
|
||||||
|
|
||||||
|
module.run_generate(generate_arguments, global_arguments)
|
|
@ -962,6 +962,81 @@ def test_get_local_path_without_local_path_defaults_to_borg():
|
||||||
assert module.get_local_path({'test.yaml': {'location': {}}}) == 'borg'
|
assert module.get_local_path({'test.yaml': {'location': {}}}) == 'borg'
|
||||||
|
|
||||||
|
|
||||||
|
def test_collect_highlander_action_summary_logs_info_for_success_with_bootstrap():
|
||||||
|
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||||
|
flexmock(module.borgmatic.actions.config.bootstrap).should_receive('run_bootstrap')
|
||||||
|
arguments = {
|
||||||
|
'bootstrap': flexmock(repository='repo'),
|
||||||
|
'global': flexmock(dry_run=False),
|
||||||
|
}
|
||||||
|
|
||||||
|
logs = tuple(
|
||||||
|
module.collect_highlander_action_summary_logs({'test.yaml': {}}, arguments=arguments)
|
||||||
|
)
|
||||||
|
assert {log.levelno for log in logs} == {logging.ANSWER}
|
||||||
|
|
||||||
|
|
||||||
|
def test_collect_highlander_action_summary_logs_error_on_bootstrap_failure():
|
||||||
|
flexmock(module.borg_version).should_receive('local_borg_version').and_return(flexmock())
|
||||||
|
flexmock(module.borgmatic.actions.config.bootstrap).should_receive('run_bootstrap').and_raise(
|
||||||
|
ValueError
|
||||||
|
)
|
||||||
|
arguments = {
|
||||||
|
'bootstrap': flexmock(repository='repo'),
|
||||||
|
'global': flexmock(dry_run=False),
|
||||||
|
}
|
||||||
|
|
||||||
|
logs = tuple(
|
||||||
|
module.collect_highlander_action_summary_logs({'test.yaml': {}}, arguments=arguments)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert {log.levelno for log in logs} == {logging.CRITICAL}
|
||||||
|
|
||||||
|
|
||||||
|
def test_collect_highlander_action_summary_logs_error_on_bootstrap_local_borg_version_failure():
|
||||||
|
flexmock(module.borg_version).should_receive('local_borg_version').and_raise(ValueError)
|
||||||
|
flexmock(module.borgmatic.actions.config.bootstrap).should_receive('run_bootstrap').never()
|
||||||
|
arguments = {
|
||||||
|
'bootstrap': flexmock(repository='repo'),
|
||||||
|
'global': flexmock(dry_run=False),
|
||||||
|
}
|
||||||
|
|
||||||
|
logs = tuple(
|
||||||
|
module.collect_highlander_action_summary_logs({'test.yaml': {}}, arguments=arguments)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert {log.levelno for log in logs} == {logging.CRITICAL}
|
||||||
|
|
||||||
|
|
||||||
|
def test_collect_highlander_action_summary_logs_info_for_success_with_generate():
|
||||||
|
flexmock(module.borgmatic.actions.config.generate).should_receive('run_generate')
|
||||||
|
arguments = {
|
||||||
|
'generate': flexmock(destination='test.yaml'),
|
||||||
|
'global': flexmock(dry_run=False),
|
||||||
|
}
|
||||||
|
|
||||||
|
logs = tuple(
|
||||||
|
module.collect_highlander_action_summary_logs({'test.yaml': {}}, arguments=arguments)
|
||||||
|
)
|
||||||
|
assert {log.levelno for log in logs} == {logging.ANSWER}
|
||||||
|
|
||||||
|
|
||||||
|
def test_collect_highlander_action_summary_logs_error_on_generate_failure():
|
||||||
|
flexmock(module.borgmatic.actions.config.generate).should_receive('run_generate').and_raise(
|
||||||
|
ValueError
|
||||||
|
)
|
||||||
|
arguments = {
|
||||||
|
'generate': flexmock(destination='test.yaml'),
|
||||||
|
'global': flexmock(dry_run=False),
|
||||||
|
}
|
||||||
|
|
||||||
|
logs = tuple(
|
||||||
|
module.collect_highlander_action_summary_logs({'test.yaml': {}}, arguments=arguments)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert {log.levelno for log in logs} == {logging.CRITICAL}
|
||||||
|
|
||||||
|
|
||||||
def test_collect_configuration_run_summary_logs_info_for_success():
|
def test_collect_configuration_run_summary_logs_info_for_success():
|
||||||
flexmock(module.command).should_receive('execute_hook').never()
|
flexmock(module.command).should_receive('execute_hook').never()
|
||||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
flexmock(module.validate).should_receive('guard_configuration_contains_repository')
|
||||||
|
@ -1000,41 +1075,6 @@ def test_collect_configuration_run_summary_logs_info_for_success_with_extract():
|
||||||
assert {log.levelno for log in logs} == {logging.INFO}
|
assert {log.levelno for log in logs} == {logging.INFO}
|
||||||
|
|
||||||
|
|
||||||
def test_collect_configuration_run_summary_logs_info_for_success_with_bootstrap():
|
|
||||||
flexmock(module.validate).should_receive('guard_single_repository_selected').never()
|
|
||||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository').never()
|
|
||||||
flexmock(module).should_receive('run_configuration').never()
|
|
||||||
flexmock(module.borgmatic.actions.config.bootstrap).should_receive('run_bootstrap')
|
|
||||||
arguments = {
|
|
||||||
'bootstrap': flexmock(repository='repo'),
|
|
||||||
'global': flexmock(monitoring_verbosity=1, dry_run=False),
|
|
||||||
}
|
|
||||||
|
|
||||||
logs = tuple(
|
|
||||||
module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments)
|
|
||||||
)
|
|
||||||
assert {log.levelno for log in logs} == {logging.ANSWER}
|
|
||||||
|
|
||||||
|
|
||||||
def test_collect_configuration_run_summary_logs_error_on_bootstrap_failure():
|
|
||||||
flexmock(module.validate).should_receive('guard_single_repository_selected').never()
|
|
||||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository').never()
|
|
||||||
flexmock(module).should_receive('run_configuration').never()
|
|
||||||
flexmock(module.borgmatic.actions.config.bootstrap).should_receive('run_bootstrap').and_raise(
|
|
||||||
ValueError
|
|
||||||
)
|
|
||||||
arguments = {
|
|
||||||
'bootstrap': flexmock(repository='repo'),
|
|
||||||
'global': flexmock(monitoring_verbosity=1, dry_run=False),
|
|
||||||
}
|
|
||||||
|
|
||||||
logs = tuple(
|
|
||||||
module.collect_configuration_run_summary_logs({'test.yaml': {}}, arguments=arguments)
|
|
||||||
)
|
|
||||||
|
|
||||||
assert {log.levelno for log in logs} == {logging.CRITICAL}
|
|
||||||
|
|
||||||
|
|
||||||
def test_collect_configuration_run_summary_logs_extract_with_repository_error():
|
def test_collect_configuration_run_summary_logs_extract_with_repository_error():
|
||||||
flexmock(module.validate).should_receive('guard_configuration_contains_repository').and_raise(
|
flexmock(module.validate).should_receive('guard_configuration_contains_repository').and_raise(
|
||||||
ValueError
|
ValueError
|
||||||
|
|
Loading…
Reference in a new issue