From 8a63c494984425f649375475b07215c9614598f6 Mon Sep 17 00:00:00 2001 From: Divyansh Singh Date: Thu, 23 Mar 2023 01:01:26 +0530 Subject: [PATCH] feat: tag repos --- borgmatic/actions/create.py | 8 +++---- borgmatic/commands/borgmatic.py | 18 ++++++++-------- borgmatic/config/normalize.py | 30 +++++++++++++++++++++----- borgmatic/config/schema.yaml | 37 +++++++++++++++++++++------------ borgmatic/config/validate.py | 11 ++++++---- 5 files changed, 69 insertions(+), 35 deletions(-) diff --git a/borgmatic/actions/create.py b/borgmatic/actions/create.py index 96a4852..1ef0eca 100644 --- a/borgmatic/actions/create.py +++ b/borgmatic/actions/create.py @@ -42,11 +42,11 @@ def run_create( global_arguments.dry_run, **hook_context, ) - logger.info('{}: Creating archive{}'.format(repository, dry_run_label)) + logger.info('{}: Creating archive{}'.format(repository['path'], dry_run_label)) borgmatic.hooks.dispatch.call_hooks_even_if_unconfigured( 'remove_database_dumps', hooks, - repository, + repository['path'], borgmatic.hooks.dump.DATABASE_HOOK_NAMES, location, global_arguments.dry_run, @@ -54,7 +54,7 @@ def run_create( active_dumps = borgmatic.hooks.dispatch.call_hooks( 'dump_databases', hooks, - repository, + repository['path'], borgmatic.hooks.dump.DATABASE_HOOK_NAMES, location, global_arguments.dry_run, @@ -63,7 +63,7 @@ def run_create( json_output = borgmatic.borg.create.create_archive( global_arguments.dry_run, - repository, + repository['path'], location, storage, local_borg_version, diff --git a/borgmatic/commands/borgmatic.py b/borgmatic/commands/borgmatic.py index fe07981..1f8b15f 100644 --- a/borgmatic/commands/borgmatic.py +++ b/borgmatic/commands/borgmatic.py @@ -108,7 +108,7 @@ def run_configuration(config_filename, config, arguments): repo_queue.put((repo, 0),) while not repo_queue.empty(): - repository_path, retry_num = repo_queue.get() + repository, retry_num = repo_queue.get() timeout = retry_num * retry_wait if timeout: logger.warning(f'{config_filename}: Sleeping {timeout}s before next retry') @@ -125,14 +125,14 @@ def run_configuration(config_filename, config, arguments): local_path=local_path, remote_path=remote_path, local_borg_version=local_borg_version, - repository_path=repository_path, + repository=repository, ) except (OSError, CalledProcessError, ValueError) as error: if retry_num < retries: - repo_queue.put((repository_path, retry_num + 1),) + repo_queue.put((repository, retry_num + 1),) tuple( # Consume the generator so as to trigger logging. log_error_records( - '{}: Error running actions for repository'.format(repository_path), + '{}: Error running actions for repository'.format(repository['path']), error, levelno=logging.WARNING, log_command_error_output=True, @@ -147,10 +147,10 @@ def run_configuration(config_filename, config, arguments): return yield from log_error_records( - '{}: Error running actions for repository'.format(repository_path), error + '{}: Error running actions for repository'.format(repository['path']), error ) encountered_error = error - error_repository = repository_path + error_repository = repository['path'] try: if using_primary_action: @@ -248,7 +248,7 @@ def run_actions( local_path, remote_path, local_borg_version, - repository_path, + repository, ): ''' Given parsed command-line arguments as an argparse.ArgumentParser instance, the configuration @@ -263,13 +263,13 @@ def run_actions( invalid. ''' add_custom_log_levels() - repository = os.path.expanduser(repository_path) + repository_path = os.path.expanduser(repository['path']) global_arguments = arguments['global'] dry_run_label = ' (dry run; not making any changes)' if global_arguments.dry_run else '' hook_context = { 'repository': repository_path, # Deprecated: For backwards compatibility with borgmatic < 1.6.0. - 'repositories': ','.join(location['repositories']), + 'repositories': ','.join([repo['path'] for repo in location['repositories']]), } command.execute_hook( diff --git a/borgmatic/config/normalize.py b/borgmatic/config/normalize.py index a143a19..58e3468 100644 --- a/borgmatic/config/normalize.py +++ b/borgmatic/config/normalize.py @@ -56,9 +56,13 @@ def normalize(config_filename, config): # Upgrade remote repositories to ssh:// syntax, required in Borg 2. repositories = location.get('repositories') + if isinstance(repositories[0], str): + config['location']['repositories'] = [{'path': repository} for repository in repositories] + repositories = config['location']['repositories'] if repositories: config['location']['repositories'] = [] - for repository in repositories: + for repository_dict in repositories: + repository = repository_dict['path'] if '~' in repository: logs.append( logging.makeLogRecord( @@ -71,11 +75,19 @@ def normalize(config_filename, config): ) if ':' in repository: if repository.startswith('file://'): + updated_repository_path = os.path.abspath(repository.partition('file://')[-1]) + config['location']['repositories'].append( - os.path.abspath(repository.partition('file://')[-1]) + { + 'path': updated_repository_path, + 'label': repository_dict.get('label', None), + } ) elif repository.startswith('ssh://'): - config['location']['repositories'].append(repository) + config['location']['repositories'].append({ + 'path': repository, + 'label': repository_dict.get('label', None), + }) else: rewritten_repository = f"ssh://{repository.replace(':~', '/~').replace(':/', '/').replace(':', '/./')}" logs.append( @@ -87,8 +99,16 @@ def normalize(config_filename, config): ) ) ) - config['location']['repositories'].append(rewritten_repository) + config['location']['repositories'].append({ + 'path': rewritten_repository, + 'label': repository_dict.get('label', None), + }) else: - config['location']['repositories'].append(repository) + config['location']['repositories'].append( + { + 'path': repository, + 'label': repository_dict.get('label', None), + } + ) return logs diff --git a/borgmatic/config/schema.yaml b/borgmatic/config/schema.yaml index d4d57ab..4235adb 100644 --- a/borgmatic/config/schema.yaml +++ b/borgmatic/config/schema.yaml @@ -29,19 +29,30 @@ properties: repositories: type: array items: - type: string - description: | - Paths to local or remote repositories (required). Tildes are - expanded. Multiple repositories are backed up to in - sequence. Borg placeholders can be used. See the output of - "borg help placeholders" for details. See ssh_command for - SSH options like identity file or port. If systemd service - is used, then add local repository paths in the systemd - service file to the ReadWritePaths list. - example: - - ssh://user@backupserver/./sourcehostname.borg - - ssh://user@backupserver/./{fqdn} - - /var/local/backups/local.borg + type: object + required: + - path + properties: + path: + type: string + description: | + Path to local or remote repository (required). + are expanded. Multiple repositories are backed up to + in sequence. Borg placeholders can be used. See the + output of "borg help placeholders" for details. See + ssh_command for SSH options like identity file or + port. If systemd service is used, then add local + repository paths in the systemd service file to the + ReadWritePaths list. + example: + - ssh://user@backupserver/./sourcehostname.borg + - ssh://user@backupserver/./{fqdn} + - /var/local/backups/local.borg + label: + type: string + description: | + Optional label for the repository. + example: backupserver working_directory: type: string description: | diff --git a/borgmatic/config/validate.py b/borgmatic/config/validate.py index 5828380..9b64377 100644 --- a/borgmatic/config/validate.py +++ b/borgmatic/config/validate.py @@ -138,10 +138,13 @@ def normalize_repository_path(repository): def repositories_match(first, second): ''' - Given two repository paths (relative and/or absolute), return whether they match. + Given two repository dicts with keys 'path' (relative and/or absolute), + and 'label', return whether they match. ''' - return normalize_repository_path(first) == normalize_repository_path(second) - + if isinstance(first,str) and isinstance(second,str): + return normalize_repository_path(first) == normalize_repository_path(second) + elif isinstance(first,dict) and isinstance(second,str): + return (second == first.get('label')) or (normalize_repository_path(second) == normalize_repository_path(first.get('path'))) def guard_configuration_contains_repository(repository, configurations): ''' @@ -160,7 +163,7 @@ def guard_configuration_contains_repository(repository, configurations): config_repository for config in configurations.values() for config_repository in config['location']['repositories'] - if repositories_match(repository, config_repository) + if repositories_match(config_repository, repository) ) )