diff --git a/backup.py b/backup.py index 674bae1..6a1d55d 100755 --- a/backup.py +++ b/backup.py @@ -45,6 +45,8 @@ loglvls = {'critical': logging.CRITICAL, ### DEFAULT NAMESPACE ### dflt_ns = 'http://git.root2.io/r00t2/borgextend/' +# In code, replace "day" with "daily" when constructing command. +policyperiods = ('Second', 'Minute', 'Hour', 'Day', 'Week', 'Month', 'Year') ### THE GUTS ### class Backup(object): @@ -127,9 +129,6 @@ 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 @@ -140,8 +139,8 @@ class Backup(object): r[a] = repo.attrib[a] for e in ('path', 'exclude'): # TODO: have an attrib for path and exclude, "glob="? - # If true, try using the glob module to resolve paths? - # This gives us the benefit of allowing glob per-path/exclude. + # If true, try using the glob module to resolve paths? + # This gives us the benefit of allowing glob per-path/exclude. r[e] = [i.text for i in repo.findall(self.ns + e)] for prep in repo.findall('{0}prep'.format(self.ns)): if 'prep' not in r: @@ -164,11 +163,21 @@ 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 + keepWithin = repo.find('{0}keepWithin'.format(self.ns)) + if keepWithin is not None: + if 'retention' not in r.keys(): + r['retention'] = {} + r['retention']['last'] = isodate.parse_duration(keepWithin.text) + keepLast = repo.find('{0}keepLast'.format(self.ns)) + if keepLast is not None: + for e in policyperiods: + k = e.lower() + policy = keepLast.find('{0}per{1}'.format(self.ns, e)) + if policy is not None: + if 'retention' not in r.keys(): + r['retention'] = {} + # This is safe. We validate the config. + r['retention'][k] = int(policy.text) repos.append(r) return(repos) self.logger.debug('VARS (before args cleanup): {0}'.format(vars(self))) @@ -482,12 +491,8 @@ class Backup(object): _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. + # No prune retention 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'])) @@ -500,8 +505,21 @@ class Backup(object): '--log-json', '--{0}'.format(self.args['loglevel']), 'prune', - '--stats', - '--keep-secondly', int(retentionSeconds)] + '--stats'] + # keepWithin + if repo['retention'].get('last') is not None: + if isinstance(repo['retention'].get('last'), datetime.timedelta): # it's a time.timedelta + retentionSeconds = repo['retention'].total_seconds() + else: # it's an isodate.Duration + retentionSeconds = repo['retention'].totimedelta(datetime.datetime.now()).total_seconds() + _cmd.extend(['--keep-within', '{0}H'.format(retentionSeconds / 60)]) + # keepLast + for e in policyperiods: + if repo['retention'].get(e.lower()) is not None: + a = e.lower() + 'ly' + if e.lower() == 'day': + a = 'daily' + _cmd.extend(['--keep-{0}'.format(a), repo['retention'][e.lower()]]) if self.repos[server]['remote'].lower()[0] in ('1', 't'): repo_tgt = '{0}@{1}'.format(_user, server) else: diff --git a/config.xsd b/config.xsd index f359bc1..f9cc0e1 100644 --- a/config.xsd +++ b/config.xsd @@ -43,6 +43,7 @@ + @@ -56,6 +57,24 @@ + + + + + + + + + + + + + + + + + + @@ -100,8 +119,6 @@ - - @@ -124,8 +141,6 @@ - - diff --git a/sample.config.snippet.xml b/sample.config.snippet.xml index e1710e1..e9cb309 100644 --- a/sample.config.snippet.xml +++ b/sample.config.snippet.xml @@ -1,8 +1,5 @@ - /dev/null diff --git a/sample.config.xml b/sample.config.xml index 7c8a1f8..86ce93d 100644 --- a/sample.config.xml +++ b/sample.config.xml @@ -12,17 +12,9 @@ "dummy" = a boolean; if you need to create a "dummy" server, set this to "true". It will *not* be parsed or executed upon. It won't even be created by an init operation or show up in a repolist operation. - "pruneRetention" = an ISO 8601 duration to be used as the default rentention period during a prune operation. --> - - + + - + compression="lzma,9"> + + P1MT2M30S + + + 2 + 3 + + /a - + /a/b