cleaning up Rob's remote branch for inclusion into upstream
This commit is contained in:
61
backup.py
61
backup.py
@@ -20,9 +20,13 @@ 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.
|
||||
# https://pypi.org/project/isodate/
|
||||
import isodate
|
||||
# https://pypi.org/project/lxml/
|
||||
# A lot safer and easier to use than the stdlib xml module.
|
||||
from lxml import etree
|
||||
try:
|
||||
# https://www.freedesktop.org/software/systemd/python-systemd/journal.html#journalhandler-class
|
||||
from systemd import journal
|
||||
@@ -30,6 +34,7 @@ try:
|
||||
except ImportError:
|
||||
has_systemd = False
|
||||
|
||||
|
||||
### LOG LEVEL MAPPINGS ###
|
||||
loglvls = {'critical': logging.CRITICAL,
|
||||
'error': logging.ERROR,
|
||||
@@ -38,7 +43,7 @@ loglvls = {'critical': logging.CRITICAL,
|
||||
'debug': logging.DEBUG}
|
||||
|
||||
### DEFAULT NAMESPACE ###
|
||||
dflt_ns = 'http://git.square-r00t.net/BorgExtend/tree/storage/backups/borg/'
|
||||
dflt_ns = 'http://git.root2.io/r00t2/borgextend/'
|
||||
|
||||
|
||||
### THE GUTS ###
|
||||
@@ -79,6 +84,8 @@ class Backup(object):
|
||||
if self.args['verbose']:
|
||||
handlers.append(logging.StreamHandler())
|
||||
if has_systemd:
|
||||
# There are two different modules with the same import floating around.
|
||||
# We can use either, but we need to figure out which one it is first.
|
||||
try:
|
||||
h = journal.JournalHandler()
|
||||
except AttributeError:
|
||||
@@ -120,6 +127,9 @@ class Backup(object):
|
||||
if not reponames:
|
||||
reponames = []
|
||||
repos = []
|
||||
dfltRetention = None
|
||||
if server.attrib.get('pruneRetention') is not None:
|
||||
dfltRetention = isodate.parse_duration(server.attrib.get('pruneRetention'))
|
||||
for repo in server.findall('{0}repo'.format(self.ns)):
|
||||
if reponames and repo.attrib['name'] not in reponames:
|
||||
continue
|
||||
@@ -154,6 +164,11 @@ class Backup(object):
|
||||
r['plugins'][pname]['params'][paramname] = json.loads(param.text)
|
||||
else:
|
||||
r['plugins'][pname]['params'][paramname] = param.text
|
||||
retention = repo.attrib.get('pruneRetention')
|
||||
if retention is not None:
|
||||
r['retention'] = isodate.parse_duration(retention)
|
||||
else:
|
||||
r['retention'] = dfltRetention
|
||||
repos.append(r)
|
||||
return(repos)
|
||||
self.logger.debug('VARS (before args cleanup): {0}'.format(vars(self)))
|
||||
@@ -181,6 +196,7 @@ class Backup(object):
|
||||
self.repos[sname][x] = server.attrib[x]
|
||||
self.repos[sname]['repos'] = getRepo(server, reponames = self.args['repo'])
|
||||
self.logger.debug('VARS (after args cleanup): {0}'.format(vars(self)))
|
||||
self.logger.debug('REPOS: {0}'.format(dict(self.repos)))
|
||||
return()
|
||||
|
||||
def createRepo(self):
|
||||
@@ -455,7 +471,7 @@ class Backup(object):
|
||||
return()
|
||||
|
||||
def prune(self):
|
||||
# TODO: support "--strip-components N"?
|
||||
# https://borgbackup.readthedocs.io/en/stable/usage/prune.html
|
||||
self.logger.info('START: prune')
|
||||
for server in self.repos:
|
||||
_env = os.environ.copy()
|
||||
@@ -465,6 +481,13 @@ class Backup(object):
|
||||
_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']:
|
||||
if repo.get('retention') is None:
|
||||
# No prune duration was set. Skip.
|
||||
continue
|
||||
if isinstance(repo['retention'], datetime.timedelta):
|
||||
retentionSeconds = repo['retention'].total_seconds()
|
||||
else: # it's an isodate.Duration
|
||||
retentionSeconds = repo['retention'].totimedelta(datetime.datetime.now()).total_seconds()
|
||||
_loc_env = _env.copy()
|
||||
if 'password' not in repo:
|
||||
print('Password not supplied for {0}:{1}.'.format(server, repo['name']))
|
||||
@@ -477,27 +500,14 @@ class Backup(object):
|
||||
'--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]])
|
||||
'--stats',
|
||||
'--keep-secondly', int(retentionSeconds)]
|
||||
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']))
|
||||
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.
|
||||
@@ -759,10 +769,10 @@ def parseArgs():
|
||||
remoteargs,
|
||||
fileargs])
|
||||
pruneargs = subparsers.add_parser('prune',
|
||||
help = ('Prune backups to schedule.'),
|
||||
parents = [commonargs,
|
||||
remoteargs,
|
||||
fileargs])
|
||||
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 ###
|
||||
@@ -812,6 +822,7 @@ def parseArgs():
|
||||
'repo under their respective server(s).'))
|
||||
return (args)
|
||||
|
||||
|
||||
def convertConf(cfgfile):
|
||||
oldcfgfile = re.sub('\.xml$', '.json', cfgfile)
|
||||
try:
|
||||
@@ -857,7 +868,7 @@ def convertConf(cfgfile):
|
||||
namespaces = {None: dflt_ns,
|
||||
'xsi': 'http://www.w3.org/2001/XMLSchema-instance'}
|
||||
xsi = {('{http://www.w3.org/2001/'
|
||||
'XMLSchema-instance}schemaLocation'): ('http://git.square-r00t.net/BorgExtend/plain/config.xsd')}
|
||||
'XMLSchema-instance}schemaLocation'): ('http://git.r00t2.io/r00t2/borgextend/src/branch/master/config.xsd')}
|
||||
genname = 'LXML (http://lxml.de/)'
|
||||
root = etree.Element('borg', nsmap = namespaces, attrib = xsi)
|
||||
root.append(etree.Comment(('Generated by {0} on {1} from {2} via {3}').format(sys.argv[0],
|
||||
|
||||
Reference in New Issue
Block a user