1import logging 2import os 3import subprocess 4 5from borgmatic.execute import DO_NOT_CAPTURE, execute_command 6 7logger = logging.getLogger(__name__) 8 9 10def extract_last_archive_dry_run(repository, lock_wait=None, local_path='borg', remote_path=None): 11 ''' 12 Perform an extraction dry-run of the most recent archive. If there are no archives, skip the 13 dry-run. 14 ''' 15 remote_path_flags = ('--remote-path', remote_path) if remote_path else () 16 lock_wait_flags = ('--lock-wait', str(lock_wait)) if lock_wait else () 17 verbosity_flags = () 18 if logger.isEnabledFor(logging.DEBUG): 19 verbosity_flags = ('--debug', '--show-rc') 20 elif logger.isEnabledFor(logging.INFO): 21 verbosity_flags = ('--info',) 22 23 full_list_command = ( 24 (local_path, 'list', '--short') 25 + remote_path_flags 26 + lock_wait_flags 27 + verbosity_flags 28 + (repository,) 29 ) 30 31 list_output = execute_command( 32 full_list_command, output_log_level=None, borg_local_path=local_path 33 ) 34 35 try: 36 last_archive_name = list_output.strip().splitlines()[-1] 37 except IndexError: 38 return 39 40 list_flag = ('--list',) if logger.isEnabledFor(logging.DEBUG) else () 41 full_extract_command = ( 42 (local_path, 'extract', '--dry-run') 43 + remote_path_flags 44 + lock_wait_flags 45 + verbosity_flags 46 + list_flag 47 + ( 48 '{repository}::{last_archive_name}'.format( 49 repository=repository, last_archive_name=last_archive_name 50 ), 51 ) 52 ) 53 54 execute_command(full_extract_command, working_directory=None) 55 56 57def extract_archive( 58 dry_run, 59 repository, 60 archive, 61 paths, 62 location_config, 63 storage_config, 64 local_path='borg', 65 remote_path=None, 66 destination_path=None, 67 strip_components=None, 68 progress=False, 69 extract_to_stdout=False, 70): 71 ''' 72 Given a dry-run flag, a local or remote repository path, an archive name, zero or more paths to 73 restore from the archive, location/storage configuration dicts, optional local and remote Borg 74 paths, and an optional destination path to extract to, extract the archive into the current 75 directory. 76 77 If extract to stdout is True, then start the extraction streaming to stdout, and return that 78 extract process as an instance of subprocess.Popen. 79 ''' 80 umask = storage_config.get('umask', None) 81 lock_wait = storage_config.get('lock_wait', None) 82 83 if progress and extract_to_stdout: 84 raise ValueError('progress and extract_to_stdout cannot both be set') 85 86 full_command = ( 87 (local_path, 'extract') 88 + (('--remote-path', remote_path) if remote_path else ()) 89 + (('--numeric-owner',) if location_config.get('numeric_owner') else ()) 90 + (('--umask', str(umask)) if umask else ()) 91 + (('--lock-wait', str(lock_wait)) if lock_wait else ()) 92 + (('--info',) if logger.getEffectiveLevel() == logging.INFO else ()) 93 + (('--debug', '--list', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ()) 94 + (('--dry-run',) if dry_run else ()) 95 + (('--strip-components', str(strip_components)) if strip_components else ()) 96 + (('--progress',) if progress else ()) 97 + (('--stdout',) if extract_to_stdout else ()) 98 + ('::'.join((repository if ':' in repository else os.path.abspath(repository), archive)),) 99 + (tuple(paths) if paths else ()) 100 ) 101 102 # The progress output isn't compatible with captured and logged output, as progress messes with 103 # the terminal directly. 104 if progress: 105 return execute_command( 106 full_command, output_file=DO_NOT_CAPTURE, working_directory=destination_path 107 ) 108 return None 109 110 if extract_to_stdout: 111 return execute_command( 112 full_command, 113 output_file=subprocess.PIPE, 114 working_directory=destination_path, 115 run_to_completion=False, 116 ) 117 118 # Don't give Borg local path, so as to error on warnings, as Borg only gives a warning if the 119 # restore paths don't exist in the archive! 120 execute_command(full_command, working_directory=destination_path) 121