Added XSD Support for Pruning Old Backups

This commit is contained in:
Rob Gibson
2022-05-21 06:13:08 +00:00
parent 7ef21e8059
commit 9890b671f2
4 changed files with 138 additions and 0 deletions

View File

@@ -20,6 +20,7 @@ import re
import subprocess
import sys
import tempfile
from xmlrpc.client import Server
# TODO: virtual env?
from lxml import etree # A lot safer and easier to use than the stdlib xml module.
try:
@@ -452,6 +453,79 @@ class Backup(object):
objPrinter(r, indent = 3)
print()
return()
def prune(self):
# TODO: support "--strip-components N"?
self.logger.info('START: prune')
for server in self.repos:
_env = os.environ.copy()
if self.repos[server]['remote'].lower()[0] in ('1', 't'):
_env['BORG_RSH'] = self.repos[server].get('rsh', None)
_env['LANG'] = 'en_US.UTF-8'
_env['LC_CTYPE'] = 'en_US.UTF-8'
_user = self.repos[server].get('user', pwd.getpwuid(os.geteuid()).pw_name)
for repo in self.repos[server]['repos']:
_loc_env = _env.copy()
if 'password' not in repo:
print('Password not supplied for {0}:{1}.'.format(server, repo['name']))
_loc_env['BORG_PASSPHRASE'] = getpass.getpass('Password (will NOT echo back): ')
else:
_loc_env['BORG_PASSPHRASE'] = repo['password']
self.logger.info('[{0}]: BEGIN PRUNE: {1}'.format(server, repo['name']))
# This is where we actually do the thing.
_cmd = [self.borgbin,
'--log-json',
'--{0}'.format(self.args['loglevel']),
'prune',
'--stats']
if self.repos[server]['keepYearly'][0].isnumeric() and int(self.repos[server]['keepYearly'][0]) > 0:
_cmd.extend(['--keep-yearly', self.repos[server]['keepYearly'].lower()[0]])
if self.repos[server]['keepMonthly'][0].isnumeric() and int(self.repos[server]['keepMonthly'][0]) > 0:
_cmd.extend(['--keep-monthly', self.repos[server]['keepMonthly'].lower()[0]])
if self.repos[server]['keepWeekly'][0].isnumeric() and int(self.repos[server]['keepWeekly'][0]) > 0:
_cmd.extend(['--keep-weekly', self.repos[server]['keepWeekly'].lower()[0]])
if self.repos[server]['keepDaily'][0].isnumeric() and int(self.repos[server]['keepDaily'][0]) > 0:
_cmd.extend(['--keep-daily', self.repos[server]['keepDaily'].lower()[0]])
if self.repos[server]['keepHourly'][0].isnumeric() and int(self.repos[server]['keepHourly'][0]) > 0:
_cmd.extend(['--keep-hourly', self.repos[server]['keepHourly'].lower()[0]])
if self.repos[server]['keepMinutely'][0].isnumeric() and int(self.repos[server]['keepMinutely'][0]) > 0:
_cmd.extend(['--keep-minutely', self.repos[server]['keepMinutely'].lower()[0]])
if self.repos[server]['keepSecondly'][0].isnumeric() and int(self.repos[server]['keepSecondly'][0]) > 0:
_cmd.extend(['--keep-secondly', self.repos[server]['keepSecondly'].lower()[0]])
if self.repos[server]['remote'].lower()[0] in ('1', 't'):
repo_tgt = '{0}@{1}'.format(_user, server)
else:
repo_tgt = os.path.abspath(os.path.expanduser(server))
_cmd.append('{0}:{1}'.format(repo_tgt,
repo['name']))
self.logger.debug('VARS: {0}'.format(vars()))
# We don't use self.cmdExec() here because we want to explicitly
# pass the env and format the log line differently.
self.logger.debug('[{0}]: Running command: {1}'.format(repo['name'],
' '.join(_cmd)))
if not self.args['dryrun']:
_out = subprocess.run(_cmd,
env = _loc_env,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE)
_stdout = _out.stdout.decode('utf-8').strip()
_stderr = _out.stderr.decode('utf-8').strip()
_returncode = _out.returncode
self.logger.debug('[{0}]: (RESULT) {1}'.format(repo['name'], _stdout))
self.logger.debug('[{0}]: STDERR: ({2})\n{1}'.format(repo['name'],
_stderr,
' '.join(
_cmd)))
if _returncode != 0:
self.logger.error(
'[{0}]: FAILED: {1}'.format(repo['name'], ' '.join(_cmd)))
if _stderr != '' and self.cron and _returncode != 0:
self.logger.warning('Command {0} failed: {1}'.format(' '.join(_cmd),
_stderr))
del (_loc_env['BORG_PASSPHRASE'])
self.logger.info('[{0}]: END PRUNE'.format(repo['name']))
self.logger.info('END: prune')
return()
def printer(self):
# TODO: better alignment. https://stackoverflow.com/a/5676884
@@ -684,6 +758,11 @@ def parseArgs():
parents = [commonargs,
remoteargs,
fileargs])
pruneargs = subparsers.add_parser('prune',
help = ('Prune backups to schedule.'),
parents = [commonargs,
remoteargs,
fileargs])
cvrtargs = subparsers.add_parser('convert',
help = ('Convert the legacy JSON format to the new XML format and quit'))
### OPERATION-SPECIFIC OPTIONS ###
@@ -837,6 +916,8 @@ def main():
bak.createRepo()
elif args['oper'] == 'restore':
bak.restore()
elif args['oper'] == 'prune':
bak.prune()
return()