def test_execute_command_without_capture_raises_on_error(): flexmock(module.subprocess).should_receive('check_call').and_raise( module.subprocess.CalledProcessError(2, 'borg init') ) with pytest.raises(module.subprocess.CalledProcessError): module.execute_command_without_capture(('borg', 'init'))
def test_execute_command_without_capture_does_not_raise_on_warning(): flexmock(module).should_receive('exit_code_indicates_error').and_return( False) flexmock(module.subprocess).should_receive('check_call').and_raise( module.subprocess.CalledProcessError(1, 'borg init')) module.execute_command_without_capture(('borg', 'init'))
def check_archives( repository, storage_config, consistency_config, local_path='borg', remote_path=None, progress=None, repair=None, only_checks=None, ): ''' Given a local or remote repository path, a storage config dict, a consistency config dict, local/remote commands to run, whether to include progress information, whether to attempt a repair, and an optional list of checks to use instead of configured checks, check the contained Borg archives for consistency. If there are no consistency checks to run, skip running them. ''' checks = _parse_checks(consistency_config, only_checks) check_last = consistency_config.get('check_last', None) lock_wait = None extra_borg_options = storage_config.get('extra_borg_options', {}).get('check', '') if set(checks).intersection(set(DEFAULT_CHECKS + ('data',))): lock_wait = storage_config.get('lock_wait', None) verbosity_flags = () if logger.isEnabledFor(logging.INFO): verbosity_flags = ('--info',) if logger.isEnabledFor(logging.DEBUG): verbosity_flags = ('--debug', '--show-rc') prefix = consistency_config.get('prefix', DEFAULT_PREFIX) full_command = ( (local_path, 'check') + (('--repair',) if repair else ()) + _make_check_flags(checks, check_last, prefix) + (('--remote-path', remote_path) if remote_path else ()) + (('--lock-wait', str(lock_wait)) if lock_wait else ()) + verbosity_flags + (('--progress',) if progress else ()) + (tuple(extra_borg_options.split(' ')) if extra_borg_options else ()) + (repository,) ) # The Borg repair option trigger an interactive prompt, which won't work when output is # captured. And progress messes with the terminal directly. if repair or progress: execute_command_without_capture(full_command, error_on_warnings=True) else: execute_command(full_command, error_on_warnings=True) if 'extract' in checks: extract.extract_last_archive_dry_run(repository, lock_wait, local_path, remote_path)
def extract_archive( dry_run, repository, archive, paths, location_config, storage_config, local_path='borg', remote_path=None, destination_path=None, progress=False, error_on_warnings=True, ): ''' Given a dry-run flag, a local or remote repository path, an archive name, zero or more paths to restore from the archive, location/storage configuration dicts, optional local and remote Borg paths, and an optional destination path to extract to, extract the archive into the current directory. ''' umask = storage_config.get('umask', None) lock_wait = storage_config.get('lock_wait', None) full_command = ( (local_path, 'extract') + (('--remote-path', remote_path) if remote_path else ()) + (('--numeric-owner', ) if location_config.get('numeric_owner') else ()) + (('--umask', str(umask)) if umask else ()) + (('--lock-wait', str(lock_wait)) if lock_wait else ()) + (('--info', ) if logger.getEffectiveLevel() == logging.INFO else ()) + (('--debug', '--list', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ()) + (('--dry-run', ) if dry_run else ()) + (('--progress', ) if progress else ()) + ('::'.join( (repository if ':' in repository else os.path.abspath(repository), archive)), ) + (tuple(paths) if paths else ())) # The progress output isn't compatible with captured and logged output, as progress messes with # the terminal directly. if progress: execute_command_without_capture(full_command, working_directory=destination_path, error_on_warnings=error_on_warnings) return # Error on warnings by default, as Borg only gives a warning if the restore paths don't exist in # the archive! execute_command(full_command, working_directory=destination_path, error_on_warnings=error_on_warnings)
def initialize_repository( repository, storage_config, encryption_mode, append_only=None, storage_quota=None, local_path='borg', remote_path=None, ): ''' Given a local or remote repository path, a storage configuration dict, a Borg encryption mode, whether the repository should be append-only, and the storage quota to use, initialize the repository. If the repository already exists, then log and skip initialization. ''' info_command = ( (local_path, 'info') + (('--info',) if logger.getEffectiveLevel() == logging.INFO else ()) + (('--debug',) if logger.isEnabledFor(logging.DEBUG) else ()) + (('--remote-path', remote_path) if remote_path else ()) + (repository,) ) logger.debug(' '.join(info_command)) try: execute_command(info_command, output_log_level=None) logger.info('Repository already exists. Skipping initialization.') return except subprocess.CalledProcessError as error: if error.returncode != INFO_REPOSITORY_NOT_FOUND_EXIT_CODE: raise extra_borg_options = storage_config.get('extra_borg_options', {}).get('init', '') init_command = ( (local_path, 'init') + (('--encryption', encryption_mode) if encryption_mode else ()) + (('--append-only',) if append_only else ()) + (('--storage-quota', storage_quota) if storage_quota else ()) + (('--info',) if logger.getEffectiveLevel() == logging.INFO else ()) + (('--debug',) if logger.isEnabledFor(logging.DEBUG) else ()) + (('--remote-path', remote_path) if remote_path else ()) + (tuple(extra_borg_options.split(' ')) if extra_borg_options else ()) + (repository,) ) # Don't use execute_command() here because it doesn't support interactive prompts. execute_command_without_capture(init_command, error_on_warnings=False)
def extract_archive( dry_run, repository, archive, restore_paths, location_config, storage_config, local_path='borg', remote_path=None, progress=False, ): ''' Given a dry-run flag, a local or remote repository path, an archive name, zero or more paths to restore from the archive, and location/storage configuration dicts, extract the archive into the current directory. ''' umask = storage_config.get('umask', None) lock_wait = storage_config.get('lock_wait', None) full_command = ( (local_path, 'extract') + (('--remote-path', remote_path) if remote_path else ()) + (('--numeric-owner', ) if location_config.get('numeric_owner') else ()) + (('--umask', str(umask)) if umask else ()) + (('--lock-wait', str(lock_wait)) if lock_wait else ()) + (('--info', ) if logger.getEffectiveLevel() == logging.INFO else ()) + (('--debug', '--list', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ()) + (('--dry-run', ) if dry_run else ()) + (('--progress', ) if progress else ()) + ('::'.join((repository, archive)), ) + (tuple(restore_paths) if restore_paths else ())) # The progress output isn't compatible with captured and logged output, as progress messes with # the terminal directly. if progress: execute_command_without_capture(full_command) return execute_command(full_command)
def test_execute_command_without_capture_does_not_raise_on_success(): flexmock(module.subprocess).should_receive('check_call').and_raise( module.subprocess.CalledProcessError(0, 'borg init') ) module.execute_command_without_capture(('borg', 'init'))
def create_archive( dry_run, repository, location_config, storage_config, local_path='borg', remote_path=None, progress=False, stats=False, json=False, files=False, ): ''' Given vebosity/dry-run flags, a local or remote repository path, a location config dict, and a storage config dict, create a Borg archive and return Borg's JSON output (if any). ''' sources = _expand_directories( location_config['source_directories'] + borgmatic_source_directories( location_config.get('borgmatic_source_directory'))) pattern_file = _write_pattern_file(location_config.get('patterns')) exclude_file = _write_pattern_file( _expand_home_directories(location_config.get('exclude_patterns'))) checkpoint_interval = storage_config.get('checkpoint_interval', None) chunker_params = storage_config.get('chunker_params', None) compression = storage_config.get('compression', None) remote_rate_limit = storage_config.get('remote_rate_limit', None) umask = storage_config.get('umask', None) lock_wait = storage_config.get('lock_wait', None) files_cache = location_config.get('files_cache') default_archive_name_format = '{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}' archive_name_format = storage_config.get('archive_name_format', default_archive_name_format) extra_borg_options = storage_config.get('extra_borg_options', {}).get('create', '') full_command = ( (local_path, 'create') + _make_pattern_flags( location_config, pattern_file.name if pattern_file else None) + _make_exclude_flags(location_config, exclude_file.name if exclude_file else None) + (('--checkpoint-interval', str(checkpoint_interval)) if checkpoint_interval else ()) + (('--chunker-params', chunker_params) if chunker_params else ()) + (('--compression', compression) if compression else ()) + (('--remote-ratelimit', str(remote_rate_limit)) if remote_rate_limit else ()) + (('--one-file-system', ) if location_config.get('one_file_system') else ()) + (('--numeric-owner', ) if location_config.get('numeric_owner') else ()) + (('--noatime', ) if location_config.get('atime') is False else ()) + (('--noctime', ) if location_config.get('ctime') is False else ()) + (('--nobirthtime', ) if location_config.get('birthtime') is False else ()) + (('--read-special', ) if location_config.get('read_special') else ()) + (('--nobsdflags', ) if location_config.get('bsd_flags') is False else ()) + (('--files-cache', files_cache) if files_cache else ()) + (('--remote-path', remote_path) if remote_path else ()) + (('--umask', str(umask)) if umask else ()) + (('--lock-wait', str(lock_wait)) if lock_wait else ()) + (('--list', '--filter', 'AME-') if files and not json and not progress else ()) + (('--info', ) if logger.getEffectiveLevel() == logging.INFO and not json else ()) + (('--stats', ) if stats and not json and not dry_run else ()) + (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) and not json else ()) + (('--dry-run', ) if dry_run else ()) + (('--progress', ) if progress else ()) + (('--json', ) if json else ()) + (tuple(extra_borg_options.split(' ')) if extra_borg_options else ()) + ('{repository}::{archive_name_format}'.format( repository=repository, archive_name_format=archive_name_format), ) + sources) # The progress output isn't compatible with captured and logged output, as progress messes with # the terminal directly. if progress: execute_command_without_capture(full_command, error_on_warnings=False) return if json: output_log_level = None elif (stats or files) and logger.getEffectiveLevel() == logging.WARNING: output_log_level = logging.WARNING else: output_log_level = logging.INFO return execute_command(full_command, output_log_level, error_on_warnings=False)